前言
pytest-xdist是一款優秀的分布式測試插件,它可以實現進程級別的並發,也可以實現類似於master-worker主從分布式測試。目前中文網站對於進程級別的並發介紹的比較多,對於主從分布式測試的資料少之又少。經過反復的實踐,對於主從分布式環境的部署和運行有了一定的認知,因此,在本文中將着重介紹主從分布式測試,對於進程並發只做簡單的介紹
進程並發
使用pytest命令
pytest -s -n 2 #-n 后面跟核數,如果你的CPU是4核的,那么2表示使用2個核來並發2個進程執行測試
pytest -s -n auto #auto會自動檢測你的CPU核數,如果是4核,將並發4個進程
獲取安裝包
這里面有一些Linux下的安裝包,還有我自己用來練手的demo項目WEB_AutoTest
鏈接:https://pan.baidu.com/s/14vnqtQufXr2Jj1HuACJa9A
提取碼:bgdp
其他資料沒有准備,因此在開始試驗前需要自行安裝 pytest,pytest-html,pytest-xdist,selenium
項目介紹
項目結構
WEB_AutoTest
|--test_cases
|--__init__.py
|--test_caculate.py
|--search.py
|--pytest.ini
項目介紹
test_caculate.py是讓python自己不停的做次方運算,這是試水項目,不建議一上來就執行web自動化,先搞個demo試試,能運行起來再搭建web自動化環境
# test_caculate.py
import pytest
@pytest.mark.parametrize("n", list(range(50000)))
def test_baidu(n):
a = 2 ** 32
print(f"the baidui-{n}.")
@pytest.mark.parametrize("n", list(range(50000)))
def test_sina(n):
b = 4 ** 32
print(f"the sina-{n}")
search.py的作用是使用無頭瀏覽器不停的打開百度,然后輸入關鍵字,查找網頁響應的元素,最后做斷言。當test_caculate.py通過之后,可以將其名字更改為test_search.py,將前者改成caculate.py,這樣就只會運行web自動化測試
# search.py
from selenium import webdriver
import pytest
@pytest.mark.parametrize("n", list(range(100)))
def test_baidu(n):
# 創建chrome參數對象
options = webdriver.ChromeOptions()
options.add_argument('--no-sandbox') # 解決DevToolsActivePort文件不存在的報錯
# options.add_argument('window-size=1600x900') # 指定瀏覽器分辨率
options.add_argument('--disable-gpu') # 谷歌文檔提到需要加上這個屬性來規避bug
options.add_argument('--hide-scrollbars') # 隱藏滾動條, 應對一些特殊頁面
options.add_argument('blink-settings=imagesEnabled=false') # 不加載圖片, 提升速度
options.add_argument('--headless') # 瀏覽器不提供可視化頁面. linux下如果系統不支持可視化不加這條會啟動失敗
driver = webdriver.Chrome(options=options)
driver.get('http://www.baidu.com')
title = driver.title
url = driver.current_url
#輸入百度
driver.find_element_by_id("kw").send_keys("百度")
elements = driver.find_elements_by_xpath("//h3//em")
for element in elements:
assert element.text == "百度"
#點擊百度一下
driver.find_element_by_id("su").click()
assert title == "百度一下,你就知道"
assert url == "https://www.baidu.com/"
assert type(n) == int
driver.quit()
pytest.ini是一個配置文件,稍后會說明其作用
# pytest.ini
[pytest]
#addopts = --tx socket=192.168.0.109:8888
Windows + Linux主從分布式
系統環境
一開始,我選擇了本機Windows做master,虛擬機Centos7做worker1,同時還克隆了一台Centos7作為worker2。有關的環境版本如下:
角色 | 系統 | Python版本 | ip |
---|---|---|---|
master | Windows7 | v3.7.3 | 192.168.0.101 |
worker1 | Centos7.6 | v3.8.0 | 192.168.0.109 |
worker2 | Centos7.6 | v3.8.0 | 192.168.0.126 |
同步測試用例並運行
既然要做主從分布式,那么就需要master將測試用例同步給worker。在官網上有兩種同步方式:通過遠程的SSH賬號和通過遠程的socket服務。前者我琢磨了比較久,發現怎么試都不成功,於是就直接試后者,后者在官網上的介紹基本能讓人看懂:
基本上分為兩步:
1.將socketserver.py文件上傳到你的服務器,然后這樣運行:
python socketserver.py
socketserver.py的文件在我分享的安裝包里有,只需下載下來,通過rz命令上傳到你的服務器上。我放在了/opt目錄下,隨便放在哪個目錄都行,只要你記得路徑就行。接着運行socketserver.py,socket服務就啟動了,開始監聽8888端口
2.然后在本機(Windows)上運行同步命令
pytest -d --tx socket=192.168.0.109:8888 --tx socket=192.168.0.126:8888 --rsyncdir test_cases test_cases
在工程目錄下打開命令行運行。一開始還是好的,沒多久Centos7上的socket服務就掛了
截兩張socket服務掛了的圖
worker1
worker2
有個細節值得注意pytest -d --tx socket=xxxx --rsyncdir xxxx xxxx
,這個命令在同步完之后,會自動收集用例並執行。雖然執行的過程掛了,但用例確實同步成功了。就在下圖的pyexecnetcache目錄下
關於這個服務掛了的問題,github上已經提了一些issue:
With different python versions rsync can hangs on
pytest hangs indefinitely after completing tests in parallel
通過對第一個問題的查看,發現是由於Python版本不一致導致的。接着我更新了兩台Centos7上的版本,將它們都改為Python v3.7.7,發現還是會報同樣的錯誤。然后我設想,可以再克隆一台虛擬機,三台Centos7,一個master,兩個slave,這樣行不行呢?
三台Linux主從分布式
系統環境
這里的Python版本無論是v3.7.7還是v3.8.0都行,只要一致就可以
角色 | 系統 | 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 |
分布式運行
重新運行之前,注意將項目上傳到master上
在worker1和worker2分別運行socket.server
接着進入項目目錄WEB_AutoTest,開始執行同步命令
pytest -d --tx socket=192.168.0.126:8888 --tx socket=192.168.0.136:8888 --rsyncdir test_cases test_cases
這次正常了,我們可以看到這些運行截圖
master
worker1
worker2
運行結束之后,發現總共運行用例100000條,耗時8min52s。其中,worker1運行了47540條,約占47%,耗時8min41s,worker2運行了52460條,約占52%,耗時8min40s
master
worker1
worker2
單進程運行
我現在使用master單獨運行這100000條用例,看看效果
可以看到運行100000條用例,master單進程跑只花了4min24s
多進程運行
如果我給master分配4核,跑2個進程和跑4個進程呢?
2個進程
4個進程
計算次方耗時對比
可以看出,計算型的用例,好像是CPU核數也多,時間越慢。另外分布式的作用好像也不大
環境 | 核數 | 用例數量 | 耗時 | 備注 |
---|---|---|---|---|
Centos7主從分布式 | 1 | 100000 | 8min52s | 1 Centos7 master + 2 Centos7 worker |
Centos7單進程 | 1 | 100000 | 4min22s | |
Centos7雙進程並發 | 2 | 100000 | 8min38s | |
Centos7四進程並發 | 4 | 100000 | 11min09s |
WEB自動化分布式
安裝goole-chrome-stable
將安裝包內的google-chrome-stable_current_x86_64.rpm上傳到/opt目錄下,使用yum安裝
yum install ./google-chrome-stable_current_x86_64.rpm
安裝成功后,可以使用下面的命令來查看chrome版本
[root@localhost opt]# google-chrome --version
Google Chrome 81.0.4044.122
安裝chromedriver
將安裝包內的chromedriver_linux64.zip上傳到服務器,解壓后將它放在/opt/Python-3.8.0/bin目錄下,這個是python可執行文件所在的目錄,已經配置環境變量了
同樣可以使用命令查看chromedriver對應的版本,這個版本和chrome版本一定要對應好
[root@localhost opt]# chromedriver --version
ChromeDriver 81.0.4044.69 (6813546031a4bc83f717a2ef7cd4ac6ec1199132-refs/branch-heads/4044@{#776})
注意:以上說的chrome和chromedriver,三台機器都需要安裝
修改模塊名
要運行WEB自動化了,不希望執行次方運算,可以使用mv命令重命名下模塊名
在一切就緒前,先在本地跑個簡單的程序試試水,建議把test_search.py中的參數化改成1次,直接在master運行,看配置的環境有沒有問題
正常情況下,運行完應該斷言成功
分布式運行
在試驗之前,建議把test_search.py的參數化運行次數改成原來的100
方法和前面一樣,啟動worker上的socket服務后,使用命令
pytest -d --tx socket=192.168.0.126:8888 --tx socket=192.168.0.136:8888 --rsyncdir test_cases test_cases
可以看到,總共運行100條用例,總耗時2min42s,其中worker1運行用例52條,耗時2min42s,worker2運行用例28條,耗時2min40s
master
worker1
worker2
單進程運行
運行用例100條,耗時4min57s
多進程運行
2個進程
100條用例耗時2min50s
4個進程
4個進程甚至更快,總耗時才1min40s
不過還要考慮一種情況,就是分布式有個同步用例的過程,現在我把同步用例的過程去掉,試試分布式的時間。看樣子也要2min32s
WEB運行耗時對比
環境 | 核數 | 用例數量 | 耗時 | 備注 |
---|---|---|---|---|
Centos7主從分布式 | 1 | 100 | 2min42s | 1 Centos7 master + 2 Centos7 worker |
Centos7單進程 | 1 | 100 | 4min57s | |
Centos7雙進程並發 | 2 | 100 | 2min50s | |
Centos7四進程並發 | 4 | 100 | 1min40s | |
Centos7主從分布式不同步 | 1 | 100 | 2min32s |
配置文件
和pytest的pytest.ini一樣,你可以在pytest.ini中做一些配置,比如想要三個進程執行就可以使用配置
[pytest]
addopts = -n3
如果之前已經同步了一次測試用例,這次想要直接運行,但又不想跟特別長的--tx怎么辦
[pytest]
addopts = --tx socket=192.168.0.126:8888 --tx=socket=192.168.0.136:8888
這樣配置之后,你可以根據情況來選擇運行
pytest --dist=each
或者
pytest --dist=load
each模式和load模式
這兩個模式的解釋最后才找到,官網藏得比較隱蔽。你可以點擊這里查看-->dist的模式
--dist=each:master將所有的測試用例都分發到各個worker上,相當於worker1執行所有的100條用例,worker2執行所有的100條用例。運行完master上顯示的總用例個數是200條
--dist=load:master先將25%的用例以循環的方式分發給每個worker,剩下的用例等worker執行完之后再分發。有點nginx負載均衡的感覺,worker負載過高,master會將其負載降低一些,讓其他worker分攤一點。這種模式運行的總用例個數是100條,worker1和worker2運行的用例數不定,有可能各是50條,有可能一個是48條,一個是52條。就像我們之前看到的那樣
使用pytest -d --tx socket=xxxx rsyncdir xxxx xxxx
這種同步方式運行,默認的是load模式
APP自動化分布式設想
WEB自動化之所以簡單,是因為只要chromedriver和chrome版本一致,問題應該不是很大。但APP就不同了,APP涉及到的東西較多,首先必須要有真機或者模擬器,然后還要啟動appium服務端,在腳本中將desired_caps(比如platformName,packageName,activityName,systemVersion等)傳遞給appium服務端,再由服務端返回driver來操作客戶端
因此APP自動化也要保證環境的一致性,即所裝的APP版本,模擬器系統版本、appium的端口號都要一致,因為都是一套代碼推送到不同的worker。可以像之前那樣繼續使用docker,注意的是兩個worker的appium容器的docker_name必須一樣,這樣才可以在腳本里使用docker docker_name
的方式啟動appium服務
還有一個問題是,如果master和worker都是Centos7,就要考慮模擬器怎么和遠程的worker連起來,之前嘗試過通過tcpip來連,是可以成功的。這個tcpip端口號可以設置不同,因為desired_caps里對這個deviceName沒要求。這么來看,APP自動化也是具有可行性的
事實上,分布式測試對用例的獨立性要求很高,盡量避免不必要的依賴,這也是UI自動化設計的原則
參考文章
《pytest-xdist官網》
《Pytest系列(16)- 分布式測試插件之pytest-xdist的詳細》
《Pytest系列(17)- pytest-xdist分布式測試的原理和流程》
《CentOS7下無界面使用Selenium+chromedriver進行自動化測試》
《execnet官網》