單程序序列
執行的話會非常耗費時間。假設每條用例耗時2s,1000條就需要2000s $\approx$ 33min;還要加上用例載入、測試前/後置套件等耗時;導致測試執行效率會相對低。並行執行
;這就是一種分散式場景
來縮短測試用例的執行時間,提高效率。分散式執行用例的原則:
專案結構
測試指令碼
# test1/test_1.py
import time
def test1_test1():
time.sleep(1)
assert 1 == 1, "1==1"
def test1_test2():
time.sleep(1)
assert 1 == 1, "1==1"
class TestDemo1:
def test_inner_1(self):
time.sleep(1)
assert 1 == 1, "1==1"
class TestDemo2:
def test_inner_2(self):
time.sleep(1)
assert 1 == 1, "1==1"
# test1/inner/test_3.py
import time
def test3_test1():
time.sleep(1)
assert 1 == 1, "1==1"
def test3_test2():
time.sleep(1)
assert 1 == 1, "1==1"
# test2/test_2.py
import time
def test2_test1():
time.sleep(1)
assert 1 == 1, "1==1"
def test2_test2():
time.sleep(1)
assert 1 == 1, "1==1"
# test2/inner/test_3.py
import time
def test4_test1():
time.sleep(1)
assert 1 == 1, "1==1"
def test4_test2():
time.sleep(1)
assert 1 == 1, "1==1"
正常執行:需要8.10s
多cpu並行執行用例,直接加個-n引數即可,後面num引數就是並行數量,比如num設定為3
pytest -v -n num
引數:
多程序並行執行:耗時2.66s
大大的縮短了測試用例的執行時間。
xdist的分散式類似於一主多從的結構,master負責下發命令,控制slave;slave根據master的命令執行特定測試任務。
在xdist中,主是master,從是workers;xdist會產生一個或多個workers,workers都通過master來控制,每個worker相當於一個mini版pytest執行器
。
master不執行測試任務,只對worker收集到的所有用例進行分發;每個worker負責執行測試用例,然後將執行結果反饋給master;由master統計最終測試結果。
master在測試對談(test session)
開始前產生一個或多個worker。
實際編譯執行測試程式碼的worker可能是本地機器也可能是遠端機器。
每個worker類似一個迷你型的pytest執行器
。
worker會執行一個完整的test collection
過程。【收集所有測試用例的過程】
然後把測試用例的ids
返回給master。【ids表示收集到的測試用例路徑】
master不執行任何測試用例。
注意:分散式測試(pytest-xdist)方式執行測試時不會輸出測試用例中的print內容,因為master並不執行測試用例。
master接收到所有worker收集的測試用例集之後,master會進行一些完整性檢查,以確保所有worker都收集到一樣的測試用例集(包括順序)。
如果檢查通過,會將測試用例的ids列表轉換成簡單的索引列表,每個索引對應一個測試用例的在原來測試集中的位置。
這個方案可行的原因是:所有的節點都儲存著相同的測試用例集。
並且使用這種方式可以節省頻寬,因為master只需要告知workers需要執行的測試用例對應的索引,而不用告知完整的測試用例資訊。
有以下四種分發策略:命令列引數 --dist=mode選項
(預設load
)
each:master將完整的測試索引列表分發到每個worker,即每個worker都會執行一遍所有的用例。
load:master將大約$\frac{1}{n}$的測試用例以輪詢的方式分發到各個worker,剩餘的測試用例則會等待worker執行完測試用例以後再分發;每個用例只會被其中一個worker執行一次。
loadfile:master分發用例的策略為按ids
中的檔名(test_xx.py/xx_test.py)進行分發,即同一個測試檔案中的測試用例只會分發給其中一個worker;具有一定的隔離性。
loadscope:master分發用例對策略為按作用域進行分發,同一個模組下的測試函數或某個測試類中的測試函數會分發給同一個worker來執行;即py檔案中無測試類的話(只有測試function)將該模組分發給同一個worker執行,如果有測試類則會將該檔案中的測試類只會分發給同一個worker執行,多個類可能分發給多個worker;目前無法自定義分組,按類 class 分組優先於按模組 module 分組。
注意:可以使用pytest_xdist_make_scheduler
這個hook來實現自定義測試分發邏輯。
如:想按目錄級別來分發測試用例:
from xdist.scheduler import LoadScopeScheduling
class CustomizeScheduler(LoadScopeScheduling):
def _split_scope(self, nodeid):
return nodeid.split("/", 1)[0]
def pytest_xdist_make_scheduler(config, log):
return CustomizeScheduler(config, log)
xdist.scheduler.LoadScopeScheduling
並重寫_split_scope
方法pytest_xdist_make_scheduler
pytest -v -n 4 --dist=loadfile
pytest_runtestloop
:pytest的預設實現是迴圈執行所有在test_session
這個物件裡面收集到的測試用例。pytest_runtest_protocol
。pytest_runtest_protocol(item, nextitem)
hook的引數要求,為了將nextitem
傳給hook。pytest_runtest_protocol
,因為這時nextitem
引數已經可以確定。shutdown
訊號, 那麼就將nextitem
引數設為None
, 然後執行 pytest_runtest_protocol
shutdown
訊號給所有worker。pytest hooks
比如:pytest_runtest_logstart
、pytest_runtest_logreport
確保整個測試活動進行正常運作。注意:pytest-xdist 是讓每個 worker 程序執行屬於自己的測試用例集下的所有測試用例。這意味著在不同程序中,不同的測試用例可能會呼叫同一個 scope 範圍級別較高(例如session)的 fixture,該 fixture 則會被執行多次,這不符合 scope=session 的預期。
pytest-xdist 沒有內建的支援來確保對談範圍的 fixture 僅執行一次,但是可以通過使用鎖定檔案進行程序間通訊來實現;讓scope=session 的 fixture 在 test session 中僅執行一次。
範例:需要安裝 filelock 包,安裝命令pip install filelock
FileLock
僅產生一次fixture資料。import pytest
from filelock import FileLock
@pytest.fixture(scope="session")
def login(tmp_path_factory, worker_id):
# 代表是單機執行
if worker_id == "master":
token = str(random())
print("fixture:請求登入介面,獲取token", token)
os.environ['token'] = token
return token
# 分散式執行
# 獲取所有子節點共用的臨時目錄,無需修改【不可刪除、修改】
root_tmp_dir = tmp_path_factory.getbasetemp().parent
fn = root_tmp_dir / "data.json"
with FileLock(str(fn) + ".lock"):
if fn.is_file(): # 代表已經有程序執行過該fixture
token = json.loads(fn.read_text())
else: # 代表該fixture第一次被執行
token = str(random())
fn.write_text(json.dumps(token))
# 最好將後續需要保留的資料存在某個地方,比如這裡是os的環境變數
os.environ['token'] = token
return token
用於並行
和並行
測試的 pytest 外掛
pip install pytest-parallel
--workers=n
:多程序執行需要加此引數, n是程序數。預設為1
--tests-per-worker=n
:多執行緒需要新增此引數,n是執行緒數
如果兩個引數都設定了,就是程序並行;每個程序最多n個執行緒,匯流排程數:程序數*執行緒數
【注意】
在windows上程序數永遠為1。
需要使用 if name == 「main」 :
在命令列視窗執行測試用例會報錯
範例:
import pytest
def test_01():
print('測試用例1操作')
def test_02():
print('測試用例2操作')
def test_03():
print('測試用例3操作')
def test_04():
print('測試用例4操作')
def test_05():
print('測試用例5操作')
def test_06():
print('測試用例6操作')
def test_07():
print('測試用例7操作')
def test_08():
print('測試用例8操作')
if __name__ == "__main__":
pytest.main(["-s", "test_b.py", '--workers=2', '--tests-per-worker=4'])
簡而言之,pytest-xdist
並行性pytest-parallel
是並行性和並行性。