使用pytest-xdist實現分布式APP自動化測試:基於SSH


前言

pytest-xdist是一款分布式測試插件,它有兩種方式實現master和worker的遠程通訊,一種是SSH,另一種是socket。本文將介紹如何使用SSH實現用例同步、用例執行以及報告收集

項目環境

系統環境

角色 系統 Python版本 ip
master Centos7.6 v3.8.0 192.168.0.109
worker1 Centos7.6 v3.8.0 192.168.0.126
worker2 Centos7.6 v3.8.0 192.168.0.136

客戶端環境

測試機 系統 ip tcpip
逍遙模擬器-1 v7.1.2 192.168.0.112 6666
逍遙模擬器-2 v7.1.2 192.168.0.113 6666

運行環境

容器 版本 端口號 宿主機器
appium_2 v1.17.0 4725->4723 worker1
appium_2 v1.17.0 4725->4723 worker2

項目結構

主要的測試用例存儲在test_case.py中,main.py是運行入口

APP_Xdist_AutoTest
   |--allure_reports
   |--common
      |--base_driver.py
      ...
   |--caps
      |--cpas.py
   |--images
   |--html_reports
   |--logs
   |--page_objects
      |--base_page.py
      |--login_page.py
      ...
   |--test_cases
      |--conftest.py
      |--test_login.py
      ...
   |--test_datas
      |--login_data.py
   |--pytest.ini
   |--main.py
   |--README.md
   |--requirements.txt

准備工作

掛載報告目錄

因為pytest-xdist對pytest-html支持較好,對allure_pytest支持的不太好。使用allure運行結束后會出現一個問題,即生成html報告要用到的json和txt數據都分散在各個worker的報告目錄下,master上報告目錄下是空的。因此需要將各個worker的報告目錄掛載到master的報告目錄,運行過程中,對worker的報告目錄寫入操作,實際上就是寫入master
掛載目錄前需要安裝一個sshfs,如何安裝sshfs請戳這里-->《使用sshfs掛載遠程文件系統》

臨時掛載和開機自動掛載

臨時掛載,重啟后掛載的信息就沒了,臨時掛載命令是:

sshfs root@192.168.0.109:/var/lib/jenkins/workspace/APP_Xdist_AutoTest/allure_reports /opt/pyexecnetcache/APP_Xdist_AutoTest/allure_reports

前面的路徑是你要掛載到的master上的報告目錄,后面的路徑是worker上的報告目錄,這個命令就是把worker的報告目錄掛載到master上
注意兩個問題:
1.掛載操作是在worker上執行
2.必須保證/opt/pyexecnetcache/APP_Xdist_AutoTest/allure_reports這個路徑中的allure_reports是空目錄,否則掛載會報錯
3.臨時掛載中會要求輸入密碼,怎么設置公鑰實現無密碼掛載,請參考上面那篇文章

開機自動掛載,又稱為永久掛載,就是開機后會自動執行shell腳本,shell腳本中是掛載命令,所以開機看到的就是已掛載好的情況。怎么設置開機自動掛載,主要有以下幾步:
1.在/opt目錄下,新建一個mount.sh的文件,往里面寫入掛載命令

# mount.sh
sshfs root@192.168.0.109:/var/lib/jenkins/workspace/APP_Xdist_AutoTest/allure_reports /opt/pyexecnetcache/APP_Xdist_AutoTest/allure_reports

2.給mount.sh賦予可執行權限

chmod +x mount.sh

3.將sh mount.sh寫入/etc/rc.local中,/etc/rc.local是/etc/rc.d/rc.local的軟連接,后者是用於添加開機自啟動命令

echo sh /opt/mount.sh >> /etc/rc.local

4.由於centos7中/etc/rc.d/rc.local降級了,不具備可執行權限,因為要給/etc/rc.local賦予可執行權限

chmod 777 /etc/rc.local

模擬器設置

1.啟動兩個系統版本一致(v7.1.2)的逍遙模擬器
2.設置橋接,設置方法請戳這里-->《docker創建appium容器並連接夜神模擬器》
3.設置tcpip端口號

C:\Users\beck>adb devices
List of devices attached
192.168.0.112:62001 device
192.168.0.113:62026 device

C:\Users\beck>adb -s 192.168.0.112:62001 tcpip 6666
restarting in TCP mode port: 6666
C:\Users\beck>adb -s 192.168.0.113:62026 tcpip 6666
restarting in TCP mode port: 6666

啟動服務

這里的步驟是兩個worker都要執行的,只不過通過tcpip連接模擬器的時候,一個worker連接一個,當看到類似"connected to 192.168.0.112:6666"的提示出現時,說明已經連接成功了

# 關閉/禁用防火牆
systemctl stop firewalld
systemctl disable firewalld

# 啟動docker容器
systemctl start docker

# 啟動appium_2容器
docker start appium_2

# 通過tcpip連接模擬器
# worker1
docker exec -it appium_2 adb connect 192.168.0.112:6666

# worker2
docker exec -it appium_2 adb connect 192.168.0.113:6666

同步運行

同步方式

在開始同步前,在master上生成rsa公鑰,然后將公鑰分別拷貝到兩個worker上,這樣ssh同步測試用例時,才會禁用輸入密碼的操作。同樣,怎么配置請戳這里-->《使用sshfs掛載遠程文件系統》
官網中給出的操作很簡單,就一句命令

pytest -d --tx ssh=myhostpopen --rsyncdir mypkg mypkg

這是pytest-xdist值得吐槽的地方,插件是好插件,文檔寫的太簡略了。一開始我也不知道ssh怎么實現同步,直到反復看了issue和其他文檔后,才發現也不難
官方提到的一個execnet的網站很重要,在這個網站上,你可以看到pytest-xdist實現分發的重要理論依據,即它是用python自帶的庫execnet,內部實例化了一個網關,通過網關實現代碼管理和數據通信。實現(實例化)官網有很多方法,我們要用到ssh和socket都包含在里面

主要看這句話,它說ssh=wyvern//python=python3.3//chdir=mycache,在主機上wyvern上指定了一個python3.3的解釋器,后面的chdir表示遠程的進程會將mycache作為它當前的工作目錄

ssh=wyvern//python=python3.3//chdir=mycache specifies a Python3.3 interpreter on the host wyvern. The remote process will have mycache as its current working directory.

在理解這句話的基礎上,經過我反復的試驗,發現ssh同步有兩種方式:
1.指定同步目錄
以我的環境為例,我要把當前目錄下的TestDemo目錄同步到遠程的/opt/pyexecnetcache目錄下,只需要運行下面的命令。這個命令在同步的時候,也運行了測試用例

pytest -d --tx 'ssh=root@192.168.0.126//python=/opt/Python-3.8.0/bin/python3.8//chdir=/opt/pyexecnetcache' --tx 'ssh=root@192.168.0.136//python=/opt/Python-3.8.0/bin/python3.8//chdir=/opt/pyexecnetcache' --rsyncdir ./Test_Demo

master

worker1

worker2

如果不想要外面的TestDemo,只想要test_demo.py,需要將同步目錄改成--rsyncdir ./Test_Demo/*

2.不指定同步目錄
不指定同步目錄,同步過去的目錄將是root的家目錄即/root,不管你當前的所在位置是在哪

pytest -d --tx 'ssh=root@192.168.0.126//python=/opt/Python-3.8.0/bin/python3.8' --tx 'ssh=root@192.168.0.136//python=/opt/Python-3.8.0/bin/python3.8' --rsyncdir ./Test_Demo/

一開始worker1在根目錄,worker2在/root目錄,得到的結果是兩個都在/root目錄生成了pyexecnetcache目錄,pyexecnetcache目錄下是TestDemo
master

worker1

worker2

看到這里或許有點疑問,不會官網寫的是pytest -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg嗎?怎么少了最后一個mypkg?最后一個mypkg不是指定同步目錄的嗎?經過我多次試驗,最后一個mypkg沒有存在的必要,你將它命名為test,它也不會再worker上創建一個test出來,來存放pyexecnetcache。同步的目錄創建的頂層目錄永遠是pyexecnetcache

同步運行

之所以要同步項目,而不是同步測試用例,因為真實的APP項目的測試用例依賴driver的生成、測試數據、公共方法等一系列的其他包模塊,如果只是將test_cases包發過去,運行肯定都是失敗的。因此要做整個項目的同步
在這里建議將同步目錄制定為/opt/pyexecnetcache,因為掛載點就是/opt/pyexecnetcache/APP_Xdist_AutoTest/allure_reports,事實上掛載決定了測試報告目錄只能是固定的路徑

pytest -d --tx 'ssh=root@192.168.0.126//python=/opt/Python-3.8.0/bin/python3.8//chdir=/opt/pyexecnetcache' --tx 'ssh=root@192.168.0.136//python=/opt/Python-3.8.0/bin/python3.8//chdir=/opt/pyexecnetcache' --rsyncdir ./APP_Xdist_AutoTest

master

worker1

worker2

可以看到,同步成功了,運行卻失敗了。這是什么原因呢?和上面說的一樣,我們的項目依賴非常復雜,pytest -d --tx...運行的只是相當於運行pytest test_case/test_xxxx.py,這時估計driver都沒生成,所以運行失敗了
因此要用python3 main.py的方式來運行,因為driver傳命令行參數給conftest.py,conftest.py里的fixture再調用base_driver()生成driver返回給測試用例,測試用例將fixture拿到,作為參數傳給各個頁面對象,以此來驅動頁面對象獲取元素,執行相應的操作
那么,問題來了,之前一直是pytest命令行,現在寫在main.py里該怎么寫?其實pytest-xdist有一個優點就是保持了一部分pytest的使用習慣,pytest命令行怎么執行冒煙呢?pytest -m smoke,放在main.py中怎么寫: pytest.main([ "-m", "smoke"])。那么,照貓畫虎就行

# main.py
import pytest
import time
from multiprocessing import Pool
from common.clean import *


device_infos = [{"docker_name": "appium_2", "platform_version": "7.1.2", "server_port": 4725}]
cur_time = time.strftime("%Y-%m-%d_%H-%M-%S")



def run_parallel(device_info):
    pytest.main([
        "-d",
        "--tx", "ssh=root@192.168.0.126//python=/opt/Python-3.8.0/bin/python3.8//chdir=/opt/pyexecnetcache",
        "--tx", "ssh=root@192.168.0.136//python=/opt/Python-3.8.0/bin/python3.8//chdir=/opt/pyexecnetcache",
        "--rsyncdir", "./",
        "APP_Xdist_AutoTest",   #因為worker的位置是/opt/pyexecnetcache,下面還有一個APP_Xdist_AutoTest目錄,最里面才是test_cases,因此需要指定APP_Xdist_AutoTest,表示進入到APP_Xdist_AutoTest里執行pytest
        f"--cmdopt={device_info}",
        #"--junitxml", f"{html_reports_dir}/autotest_report_{cur_time}.xml",
        #"--html", f"{html_reports_dir}/autotest_report_{cur_time}.html",
        #"--css", f"{html_reports_dir}/assets/style.css",
        #"--self-contained-html",
        "--alluredir", allure_reports_dir
       ])


os.system(f"allure generate {allure_reports_dir} -o {allure_reports_dir}/html --clean")


if __name__ == "__main__":
    with Pool(1) as pool:
       pool.map(run_parallel, device_infos)
       pool.close()
       pool.join()

然后單獨運行python3 main.py就能成功。13個用例耗時3min35s,之前用單個機器運行是6min49s,可以看到三台分布式運行APP自動化應該能節省近一半的時間

報告預覽

服務器上導出的報告容易出現樣式缺失,因此要將項目放在jenkins上運行,得到在線報告

參考文章

《pytest-xdist官網》
《execnet官網》
《使用SSHFS掛載遠程VPS目錄並設置開機啟動》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM