背景: 根據以下簡單的代碼示例,我們將從源碼的角度分析其中的關鍵載入執行步驟,對pytest整體流程架構有個初步學習。 代碼示例: import pytest def test_add(): assert 1 + 1 == 2 def test_sub(): assert 2 - 1 == 1 通過 ...
背景:
根據以下簡單的代碼示例,我們將從源碼的角度分析其中的關鍵載入執行步驟,對pytest整體流程架構有個初步學習。
代碼示例:
import pytest def test_add(): assert 1 + 1 == 2 def test_sub(): assert 2 - 1 == 1
通過 pytest test_example.py 運行此代碼示例後,會觸發pytest的入口函數main(),這個函數定義在src/pytest/__main__.py中,它的作用是創建一個PytestConfig對象,並調用其
do_configure()和do_main()方法。PytestConfig對象是pytest的核心配置類,它負責解析命令行參數、讀取配置文件、註冊插件、創建Session對象等。PytestConfig對象定義在
src/_pytest/config/__init__.py中,它繼承了pluggy.HookimplMarker類,也就是說它可以作為一個插件管理器,調用各種hook函數。
```python # src/pytest/__main__.py def main(): # 創建PytestConfig對象 config = PytestConfig() # 調用config.do_configure()方法 config.do_configure() # 調用config.do_main()方法 config.do_main() ``` ```python # src/_pytest/config/__init__.py class PytestConfig(pluggy.HookimplMarker): def __init__(self): # 解析命令行參數 self.parse_args() # 讀取配置文件 self.read_config_files() # 註冊插件 self.register_plugins() # 創建Session對象 self.session = Session(self) def do_configure(self): # 調用hook函數pytest_configure self.hook.pytest_configure(config=self) def do_main(self): # 調用hook函數pytest_sessionstart self.hook.pytest_sessionstart(session=self.session) # 調用Session對象的main()方法 self.session.main() ```
Session對象是pytest的核心上下文類,它負責管理整個測試過程的信息,包括收集測試用例、執行測試用例、生成測試報告等。Session對象定義在src/_pytest/main.py中,它繼承了
Collector類,也就是說它可以作為一個測試用例收集器。Session對象的main()方法是執行測試用例的主要入口,它會調用perform_collect()方法來收集測試用例,並返回一個列表items;然後
調用runtestloop()方法來迴圈執行items中的每個Item對象;最後調用hook函數pytest_sessionfinish來結束測試會話,並返回一個退出碼exitstatus。
```python # src/_pytest/main.py class Session(Collector): def __init__(self, config): # 初始化一些屬性和狀態信息 def main(self): # 調用perform_collect()方法收集測試用例,並返回items列表 items = self.perform_collect() # 調用runtestloop()方法迴圈執行items中的每個Item對象,並返回退出碼exitstatus exitstatus = self.runtestloop(items) # 調用hook函數pytest_sessionfinish來結束測試會話,並返回退出碼exitstatus self.hook.pytest_sessionfinish(session=self, exitstatus=exitstatus) return exitstatus def perform_collect(self): # 調用hook函數pytest_collectstart表示開始收集測試用例 self.hook.pytest_collectstart(collector=self) # 調用自身的collect()方法來遞歸遍歷指定的測試文件或目錄,並返回一個列表items items = self.collect() # 調用hook函數pytest_collectreport表示收集測試用例結束,並生成收集報告 self.hook.pytest_collectreport(report=CollectReport(self, "passed", items)) # 調用hook函數pytest_collection_modifyitems允許對收集到的Item對象進行修改 self.hook.pytest_collection_modifyitems(session=self, config=self.config, items=items) # 調用hook函數pytest_deselected表示從收集到的Item對象中篩選出需要執行的Item對象 self.hook.pytest_deselected(items=self.deselected) # 調用hook函數pytest_collection_finish表示收集和篩選測試用例完成,並返回最終要執行的Item對象列表 self.hook.pytest_collection_finish(session=self) return items def runtestloop(self, items): # 調用hook函數pytest_runtestloop表示開始迴圈執行測試用例 self.hook.pytest_runtestloop(session=self) # 遍歷items列表,依次取出每個Item對象 for item in items: # 調用Item對象的runtestprotocol()方法來執行單個測試用例的協議 item.runtestprotocol() # 返回退出碼0表示成功 return 0 ```
Item對象是pytest的核心測試類,它負責封裝和執行單個測試用例的信息,包括名稱、位置、參數化信息、標記信息等。Item對象定義在src/_pytest/python.py中,它繼承了Node類,也
就是說它可以作為一個測試節點。Item對象的runtestprotocol()方法是執行單個測試用例的主要入口,它會調用hook函數pytest_runtest_logstart來開始記錄日誌信息;然後調用runtest()方法
來執行測試用例的前置、主體和後置部分;最後調用hook函數pytest_runtest_logfinish來結束記錄日誌信息,並生成日誌報告。
```python # src/_pytest/python.py class Item(Node): def __init__(self, name, parent, config, session): # 初始化一些屬性和狀態信息 def runtestprotocol(self): # 調用hook函數pytest_runtest_logstart表示開始記錄日誌信息 self.ihook.pytest_runtest_logstart(nodeid=self.nodeid, location=self.location) # 調用runtest()方法來執行測試用例的前置、主體和後置部分,並返回一個列表reports reports = self.runtest() # 調用hook函數pytest_runtest_logfinish表示結束記錄日誌信息,並生成日誌報告 self.ihook.pytest_runtest_logfinish(nodeid=self.nodeid, location=self.location) return reports def runtest(self): # 創建一個空列表reports reports = [] # 調用_setup()方法來執行測試用例的前置操作,並將返回的報告添加到reports列表中 reports.append(self._setup()) # 如果前置操作沒有失敗或跳過,則調用_call()方法來執行測試用例的主體部分,並將返回的報告添加到reports列表中 if reports[-1].passed: reports.append(self._call()) # 調用_teardown()方法來執行測試用例的後置操作,並將返回的報告添加到reports列表中 reports.append(self._teardown()) # 返回reports列表 return reports def _setup(self): # 調用hook函數pytest_runtest_setup表示開始執行前置操作,並返回一個報告setup_report setup_report = self.ihook.pytest_runtest_setup(item=self) return setup_report def _call(self): # 調用hook函數pytest_runtest_call表示開始執行主體部分,並返回一個報告call_report call_report = self.ihook.pytest_runtest_call(item=self) return call_report def _teardown(self): # 調用hook函數pytest_runtest_teardown表示開始執行後置操作,並返回一個報告teardown_report teardown_report = self.ihook.pytest_runtest_teardown(item=self) return teardown_report ```
總結:
Pytest的載入流程大致如下:
- Pytest首先會解析命令行參數,確定要執行的測試文件、測試目錄、測試類、測試函數等,以及一些配置選項。
- Pytest會根據配置文件(pytest.ini、setup.cfg、tox.ini等)和命令行參數,創建一個Config對象,用於存儲配置信息。
- Pytest會創建一個Session對象,用於管理整個測試過程的上下文信息,包括收集測試用例、執行測試用例、生成測試報告等。
- Pytest會調用hook函數pytest_sessionstart,表示測試會話開始。
- Pytest會調用hook函數pytest_collectstart,表示開始收集測試用例。
- Pytest會根據Config對象中的信息,遞歸遍歷指定的測試文件或目錄,尋找符合pytest約定的測試用例(以test_開頭的函數或方法,以Test開頭的類等)。
- Pytest會將找到的測試用例封裝成Item對象,並添加到Session對象的items列表中。Item對象包含了測試用例的名稱、位置、參數化信息、標記信息等。
- Pytest會調用hook函數pytest_collectreport,表示收集測試用例結束,並生成收集報告。
- Pytest會調用hook函數pytest_collection_modifyitems,允許對收集到的Item對象進行修改,例如重新排序、添加或刪除標記等。
- Pytest會調用hook函數pytest_deselected,表示從收集到的Item對象中篩選出需要執行的Item對象,並將不需要執行的Item對象放入Session對象的deselected列表中。
- Pytest會調用hook函數pytest_collection_finish,表示收集和篩選測試用例完成,並返回最終要執行的Item對象列表。
- Pytest會根據是否使用多進程或多線程模式,創建相應的WorkerController對象,用於管理多個Worker對象。Worker對象負責執行具體的測試用例,並將結果返回給WorkerController對象。
- Pytest會調用hook函數pytest_runtestloop,表示開始迴圈執行測試用例。
- Pytest會遍歷Session對象中的items列表,依次取出每個Item對象,並調用hook函數pytest_runtest_protocol,表示開始執行單個測試用例的協議。
- Pytest會調用hook函數pytest_runtest_logstart,表示開始記錄單個測試用例的日誌信息。
- Pytest會調用hook函數pytest_runtest_setup,表示開始執行單個測試用例的前置操作(例如setup函數或方法)。
- Pytest會調用hook函數pytest_runtest_call,表示開始執行單個測試用例的主體部分(例如測試函數或方法)。
- Pytest會調用hook函數pytest_runtest_teardown,表示開始執行單個測試用例的後置操作(例如teardown函數或方法)。
- Pytest會調用hook函數pytest_runtest_logfinish,表示結束記錄單個測試用例的日誌信息,並生成日誌報告。
- Pytest會調用hook函數pytest_runtest_makereport,表示根據單個測試用例的執行結果,生成測試報告(包括setup、call和teardown三個階段的報告)。
- Pytest會重覆上述步驟,直到所有的Item對象都被執行完畢。
- Pytest會調用hook函數pytest_sessionfinish,表示測試會話結束,並生成最終的測試報告(包括所有Item對象的報告)。
- Pytest會調用hook函數pytest_terminal_summary,表示在終端輸出最終的測試結果和統計信息。