這是時常被問到的問題,尤其是UI自動化的運行,過程非常耗時,所以,所以多線程不失為一種首先想到的解決方案。
多線程是針對的測試用例,所以和selenium沒有直接關系,我們要關心的是單元測試框架。
unittest
首先,應該說明的是unittest本身是不支持多線程的。當然,如果你學過Python的threading模塊,也未必不行。不過我在stackoverflow
找了半天,大多是介紹unittest 測試多線程模塊,並非是unittest本身如何多線程運行用例。
“我如何學習葵花寶典” 和 “我如何驗證
張三
學會了葵花寶典”是兩回事,而我顯然要解決的問題是前者。
又重新百度,結果就找了答案。核心借助 tomorrow3
的測試庫,再配合HTMLTestRunner
生成測試報告。
https://github.com/SeldomQA/HTMLTestRunner
https://github.com/dflupu/tomorrow3
- 測試用例
測試用例如下,你可以復制多份測試。
# test_a.py
import time
import unittest
from selenium import webdriver
class Test1(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome()
def test_01(self):
self.driver.get("https://www.bing.com/?mkt=zh-CN")
elem = self.driver.find_element_by_id("sb_form_q")
elem.send_keys("多線程")
elem.submit()
time.sleep(2)
self.assertIn("多線程", self.driver.title)
@classmethod
def tearDownClass(cls):
cls.driver.quit()
if __name__ == "__main__":
unittest.main()
- 運行文件
核心在tomorrow3
提供的threads
裝飾器,用於裝飾測試運行方法。
import unittest
import os
from TestRunner import HTMLTestRunner
from tomorrow3 import threads
# 定義目錄
BASE_DIR = os.path.dirname(os.path.realpath(__file__))
TEST_DIR = os.path.join(BASE_DIR, "test_dir")
REPORT_DIR = os.path.join(BASE_DIR, "test_report")
def test_suits():
"""
加載所有的測試用例
"""
discover = unittest.defaultTestLoader.discover(
TEST_DIR,
pattern="test_*.py"
)
return discover
@threads(2) # !!!核心!!!! 設置線程數
def run_case(all_case, nth=0):
"""
執行所有的用例, 並把結果寫入測試報告
"""
report_abspath = os.path.join(REPORT_DIR, f"result{nth}.html")
with open(report_abspath, "wb+") as file:
runner = HTMLTestRunner(stream=file, title='多線程測試報告')
# 調用test_suits函數返回值
runner.run(all_case)
if __name__ == "__main__":
cases = test_suits()
# 循環啟動線程
for i, j in zip(cases, range(len(list(cases)))):
run_case(i, nth=j) # 執行用例,生成報告
總結:
確實可以(根據設置的線程數)同時開啟兩個瀏覽器運行,同時帶來了兩個問題。
- 程序會根據線程數,生成多份測試報告,每個測試報告統計當前線程所運行的測試結果。
- 如果去掉
nth
參數,設置為一個報告,那么第二個線程運行的結果不能正確展示的一張報告上。 - 至少每個線程中的用例要保證有瀏覽器的啟動/關閉。
第3點,你可能不理解,我舉個例子,小時候見過媽媽/奶奶縫衣服。假設一件衣服拿一根針來縫制,縫衣服最麻煩就是穿針引線,針眼很小,每次都要費半天功夫,小孩子眼神好,我時常被叫去穿針引線。那為了節約時間怎么辦,我當然是把線弄的長長的,最好是一根針線可以縫制多件衣服。
在Selenium 中,定義的瀏覽器驅動
driver
,每一條用例都定義一次驅動,伴隨而來的就每個用例都開啟/關閉一次瀏覽器,這當然是非常耗時的,為了縮短這個時間,我們最好是啟動一次瀏覽器把所有用例都跑完才關閉。那么問題來了,當我們使用多線程之后,相當於多個人同時縫制衣服,共用一根針線肯定不行啊。至少需要每人一根針線吧!而且縫制衣服的人是變化的,為了趕時間就多兩個人(多開兩個線程),用例比較少就少幾個人(少啟兩個線程),為了適應這種變化,那么最好一件衣配置一根針線,每件衣服是最小單位,一個人必須要獨立的把一件衣服縫好。
為了使用多線程,我們就必須每條用例開啟/關閉一次瀏覽器,這其實是另一種時間的浪費,為了彌補這里的浪費,你必須把線程開得足夠多才行。
最后,你必須再配置一位統計員,去統計每個人縫制衣服的數量,合計到一個報告中。
pytest
多線程運行用例在 pytest 中就相對容易太多了,pytest-xdist
插件就可以完成這件事情。
https://github.com/pytest-dev/pytest-xdist
- 測試用例
測試用例如下,你可以復制多份測試。
# test_a.py
import time
from selenium import webdriver
def test_01():
driver = webdriver.Chrome()
driver.get("http://www.baidu.com")
elem = driver.find_element_by_id("kw")
elem.send_keys("unittest")
elem.submit()
time.sleep(2)
assert driver.title == "unittest_百度搜索"
driver.quit()
def test_02():
driver = webdriver.Chrome()
driver.get("http://www.baidu.com")
elem = driver.find_element_by_id("kw")
elem.send_keys("python")
elem.submit()
time.sleep(2)
assert driver.title == "python_百度搜索"
driver.quit()
- 運行文件
核心就是安裝 pytest-xdist
插件,通過-n
參數設置線程數即可。
import os
import pytest
# 定義目錄
BASE_DIR = os.path.dirname(os.path.realpath(__file__))
TEST_DIR = os.path.join(BASE_DIR, "test_dir")
REPORT_DIR = os.path.join(BASE_DIR, "test_report")
if __name__ == '__main__':
report_file = os.path.join(REPORT_DIR, "result.html")
pytest.main([
"-n", "2", # !!核心!!!設置2個線程
"-v", "-s", TEST_DIR,
"--html=" + report_file,
])
總結:
相對於unittest,在pytest實現多線程非常簡單,最終線線程的測試結果也可以很好在一個測試報告中展示。
- 存在的問題,正如我上面討論的,你必須為每個用例設置開啟/關閉。除了額外消耗啟動時間外,如果想統一配置用例通過哪個瀏覽器執行也會比較麻煩。
- 你的每一條用例應該保持絕對獨立,不能出現這條用例依賴上條用例的執行結果。因為,這兩條用例可能會分配由不同的線程執行。
- 正如上一條的原因,你不能控制用例的執行順序。