前言:
嗨咯鐵汁們,很久不見,我還是你們的老朋友,這里也感謝各位小伙伴的點贊和關注,你們的三連是我最大的動力哈,我也不會辜負各位的期盼,這里呢給大家出了一個我們廢話少說直接開始正文
【文章的末尾給大家留下了大量的福利】
編輯
ytest架構是什么?
首先,來看一個 pytest 的例子:
def test_a(): print(123)
collected 1 itemtest_a.py . [100%]============ 1 passed in 0.02s =======================
輸出結果很簡單:收集到 1 個用例,并且這條測試用例執(zhí)行通過。此時思考兩個問題:1.pytest 如何收集到用例的?2.pytest 如何把 python 代碼,轉(zhuǎn)換成 pytest 測試用例(又稱 item) ?
pytest如何做到收集到用例的?
這個很簡單,遍歷執(zhí)行目錄,如果發(fā)現(xiàn)目錄的模塊中存在符合“ pytest 測試用例要求的 python 對象”,就將之轉(zhuǎn)換為 pytest 測試用例。比如編寫以下 hook 函數(shù):
def pytest_collect_file(path, parent): print(“hello”, path)
hello C:UsersyuruoDesktopmpmp123mpestcase__init__.pyhello C:UsersyuruoDesktopmpmp123mpestcaseconftest.pyhello C:UsersyuruoDesktopmpmp123mpestcaseest_a.py
pytest 像是包裝盒,將 python 對象包裹起來,比如下圖:
當寫好 python 代碼時:
def test_a: print(123)
會被包裹成 Function :
可以從 hook 函數(shù)中查看細節(jié):
def pytest_collection_modifyitems(session, config, items): pass
于是,理解包裹過程就是解開迷題的關鍵。pytest 是如何包裹 python 對象的?下面代碼只有兩行,看似簡單,但暗藏玄機!
def test_a: print(123)
把代碼位置截個圖,如下:
我們可以說,上述代碼是處于“testcase包”下的 “test_a.py模塊”的“test_a函數(shù)”, pytest 生成的測試用例也要有這些信息:處于“testcase包”下的 “test_a.py模塊”的“test_a測試用例:把上述表達轉(zhuǎn)換成下圖:pytest 使用 parent 屬性表示上圖層級關系,比如 Module 是 Function 的上級, Function 的 parent 屬性如下:
: parent:
當然 Module 的 parent 就是 Package:
: parent:
這里科普一下,python 的 package 和 module 都是真實存在的對象,你可以從 obj 屬性中看到,比如 Module 的 obj 屬性如下:
如果理解了 pytest 的包裹用途,非常好!我們進行下一步討論:如何構造 pytest 的 item ?
以下面代碼為例:
def test_a: print(123)
構造 pytest 的 item ,需要:3.構建 Package4.構建 Module5.構建 Function以構建 Function 為例,需要調(diào)用其from_parent()方法進行構建,其過程如下圖:
,就可以猜測出,“構建 Function”一定與其 parent 有不小聯(lián)系!又因為 Function 的 parent 是 Module :根據(jù)下面 Function 的部分代碼(位于 python.py 文件):
class Function(PyobjMixin, nodes.Item): # 用于創(chuàng)建測試用例 @classmethod def from_parent(cls, parent, **kw): “””The public constructor.””” return super().from_parent(parent=parent, **kw) # 獲取實例 def _getobj(self): assert self.parent is not None return getattr(self.parent.obj, self.originalname) # type: ignore[attr-defined] # 運行測試用例 def runtest(self) -> None: “””Execute the underlying test function.””” self.ihook.pytest_pyfunc_call(pyfuncitem=self)
得出結論,可以利用 Module 構建 Function!其調(diào)用偽代碼如下:
Function.from_parent(Module)
既然可以利用 Module 構建 Function, 那如何構建 Module ?當然是利用 Package 構建 Module!
Module.from_parent(Package)
既然可以利用 Package 構建 Module 那如何構建 Package ?別問了,快成套娃了,請看下圖調(diào)用關系:
編輯
pytest 從 Config 開始,層層構建,直到 Function !Function 是 pytest 的最小執(zhí)行單元。手動構建 item 就是模擬 pytest 構建 Function 的過程。也就是說,需要創(chuàng)建 Config ,然后利用 Config 創(chuàng)建 Session ,然后利用 Session 創(chuàng)建 Package ,…,最后創(chuàng)建 Function。
其實沒這么復雜, pytest 會自動創(chuàng)建好 Config, Session和 Package ,這三者不用手動創(chuàng)建。
比如編寫以下 hook 代碼,打斷點查看其 parent 參數(shù):
def pytest_collect_file(path, parent): pass
如果遍歷的路徑是某個包(可從path參數(shù)中查看具體路徑),比如下圖的包:
編寫如下代碼即可構建 pytest 的 Module ,如果發(fā)現(xiàn)是 yaml 文件,就根據(jù) yaml 文件內(nèi)容動態(tài)創(chuàng)建 Module 和 module :
from _pytest.python import Module, Packagedef pytest_collect_file(path, parent): if path.ext == “.yaml”: pytest_module = Module.from_parent(parent, fspath=path) # 返回自已定義的 python module pytest_module._getobj = lambda : MyModule return pytest_module
需要注意,上面代碼利用猴子補丁改寫了 _getobj 方法,為什么這么做?Module 利用 _getobj 方法尋找并導入(import語句) path 包下的 module ,其源碼如下:
# _pytest/python.py Moduleclass Module(nodes.File, PyCollector): def _getobj(self): return self._importtestmodule()def _importtestmodule(self): # We assume we are only called once per module. importmode = self.config.getoption(“–import-mode”) try: # 關鍵代碼:從路徑導入 module mod = import_path(self.fspath, mode=importmode) except SyntaxError as e: raise self.CollectError( ExceptionInfo.from_current().getrepr(style=”short”) ) from e # 省略部分代碼…
但是,如果使用數(shù)據(jù)驅(qū)動,即用戶創(chuàng)建的數(shù)據(jù)文件 test_parse.yaml ,它不是 .py 文件,不會被 python 識別成 module (只有 .py 文件才能被識別成 module)。這時,就不能讓 pytest 導入(import語句) test_parse.yaml ,需要動態(tài)改寫 _getobj ,返回自定義的 module !因此,可以借助 lambda 表達式返回自定義的 module :
lambda : MyModule
這就涉及元編程技術:動態(tài)構建 python 的 module ,并向 module 中動態(tài)加入類或者函數(shù):
import types# 動態(tài)創(chuàng)建 modulemodule = types.ModuleType(name)def function_template(*args, **kwargs): print(123)# 向 module 中加入函數(shù)setattr(module, “test_abc”, function_template)
綜上,將自己定義的 module 放入 pytest 的 Module 中即可生成 item :
# conftest.pyimport typesfrom _pytest.python import Moduledef pytest_collect_file(path, parent): if path.ext == “.yaml”: pytest_module = Module.from_parent(parent, fspath=path) # 動態(tài)創(chuàng)建 module module = types.ModuleType(path.purebasename) def function_template(*args, **kwargs): print(123) # 向 module 中加入函數(shù) setattr(module, “test_abc”, function_template) pytest_module._getobj = lambda: module return pytest_module
創(chuàng)建一個 yaml 文件,使用 pytest 運行:
======= test session starts ====platform win32 — Python 3.8.1, pytest-6.2.4, py-1.10.0, pluggy-0.13.1rootdir: C:UsersyuruoDesktopmpplugins: allure-pytest-2.8.11, forked-1.3.0, rerunfailures-9.1.1, timeout-1.4.2, xdist-2.2.1collected 1 itemtest_a.yaml 123.======= 1 passed in 0.02s =====PS C:UsersyuruoDesktopmp>
現(xiàn)在停下來,回顧一下,我們做了什么?借用 pytest hook ,將 .yaml 文件轉(zhuǎn)換成 python module。
作為一個數(shù)據(jù)驅(qū)動測試框架,我們沒做什么?沒有解析 yaml 文件內(nèi)容!上述生成的 module ,其內(nèi)的函數(shù)如下:
def function_template(*args, **kwargs): print(123)
只是簡單打印 123 。數(shù)據(jù)驅(qū)動測試框架需要解析 yaml 內(nèi)容,根據(jù)內(nèi)容動態(tài)生成函數(shù)或類。比如下面 yaml 內(nèi)容:
test_abc: – print: 123
表達的含義是“定義函數(shù) test_abc,該函數(shù)打印 123”??梢岳?yaml.safe_load 加載 yaml 內(nèi)容,并進行關鍵字解析,其中path.strpath代表 yaml 文件的地址:
import typesimport yamlfrom _pytest.python import Moduledef pytest_collect_file(path, parent): if path.ext == “.yaml”: pytest_module = Module.from_parent(parent, fspath=path) # 動態(tài)創(chuàng)建 module module = types.ModuleType(path.purebasename) # 解析 yaml 內(nèi)容 with open(path.strpath) as f: yam_content = yaml.safe_load(f) for function_name, steps in yam_content.items(): def function_template(*args, **kwargs): “”” 函數(shù)模塊 “”” # 遍歷多個測試步驟 [print: 123, print: 456] for step_dic in steps: # 解析一個測試步驟 print: 123 for step_key, step_value in step_dic.items(): if step_key == “print”: print(step_value) # 向 module 中加入函數(shù) setattr(module, function_name, function_template) pytest_module._getobj = lambda: module return pytest_module
上述測試用例運行結果如下:
=== test session starts ===platform win32 — Python 3.8.1, pytest-6.2.4, py-1.10.0, pluggy-0.13.1rootdir: C:UsersyuruoDesktopmpplugins: allure-pytest-2.8.11, forked-1.3.0, rerunfailures-9.1.1, timeout-1.4.2, xdist-2.2.1collected 1 itemtest_a.yaml 123.=== 1 passed in 0.02s ====
當然,也支持復雜一些的測試用例:
test_abc: – print: 123 – print: 456test_abd: – print: 123 – print: 456
其結果如下:
== test session starts ==platform win32 — Python 3.8.1, pytest-6.2.4, py-1.10.0, pluggy-0.13.1rootdir: C:UsersyuruoDesktopmpplugins: allure-pytest-2.8.11, forked-1.3.0, rerunfailures-9.1.1, timeout-1.4.2, xdist-2.2.1collected 2 itemstest_a.yaml 123456.123456.== 2 passed in 0.02s ==
利用pytest創(chuàng)建數(shù)據(jù)驅(qū)動測試框架就介紹到這里啦,希望能給大家?guī)硪欢ǖ膸椭?。大家有什么不懂的地方或者有疑惑也可以留言討論哈,讓我們共同進步呦!
重點:學習資料學習當然離不開資料,這里當然也給你們準備了600G的學習資料
需要的私我關鍵字【000】免費獲取哦 注意關鍵字是:000
項目實戰(zhàn)
app項目,銀行項目,醫(yī)藥項目,電商,金融
大型電商項目
全套軟件測試自動化測試教學視頻
300G教程資料下載【視頻教程+PPT+項目源碼】
全套軟件測試自動化測試大廠面經(jīng)
python自動化測試++全套模板+性能測試
聽說關注我并三連的鐵汁都已經(jīng)升職加薪暴富了哦?。。?!