# Pytest Group multiple tests in a class Request a unique temporary directory for functional tests ## 操作指南 ### 核心 pytest 功能 如何調用pytest 如何在測試中編寫和報告斷言 如何使用固定裝置 如何用屬性標記測試函數 如何參數化夾具和測試功能 如何在測試中使用臨時目錄和文件 如何猴子補丁/模擬模塊和環境 如何運行文檔測試 如何重新運行失敗的測試並在測試運行之間保持狀態 ### 測試輸出和結果 如何處理測試失敗 管理 pytest 的輸出 如何管理日誌記錄 如何捕獲標準輸出/標準錯誤輸出 如何捕獲警告 如何使用skip和xfail處理無法成功的測試 ### 插件 如何安裝和使用插件 編寫插件 編寫鉤子函數 ### pytest 和其他測試系統 如何在現有測試套件中使用 pytest 如何使用unittest基於 pytest 的測試 如何運行為鼻子編寫的測試 如何實現 xunit 風格的設置 ### pytest 開發環境 如何設置 bash 完成 ## 閱讀步驟 - [ ] 核心 pytest 功能 - [x] 如何調用pytest - 調用pytest,從 file,class,function level - [x] 如何在測試中編寫和報告斷言 - assert - Assertions about expected exceptions - 自定義錯誤解釋 - 在conftest.py 中添加鉤子 - [x] 如何使用固定裝置 - [x] 如何用屬性標記測試函數 - [x] 如何參數化夾具和測試功能 - [x] 如何在測試中使用臨時目錄和文件 - [x] 如何猴子補丁/模擬模塊和環境 - [x] 如何運行文檔測試 - [x] 如何重新運行失敗的測試並在測試運行之間保持狀態 - [ ] 測試輸出和結果 - [ ] 如何處理測試失敗 - [ ] 管理 pytest 的輸出 - [ ] 如何管理日誌記錄 - [ ] 如何捕獲標準輸出/標準錯誤輸出 - [ ] 如何捕獲警告 - [ ] 如何使用skip和xfail處理無法成功的測試 ## Note Core pytest functionality ### Requesting fixtures 測試前固定會執行的函數 fixture 可以遞迴使用 fixture 可以重複使用 在一個test多次使用fixture並不會多次執行,而是把結果緩存 自動使用autouse=True ``` @pytest.fixture(autouse=True) def append_first(order, first_entry): return order.append(first_entry) ``` #### Scope sharing fixtures across classes, modules, packages or session ``` @pytest.fixture(scope="session") def smtp_connection(): # the returned fixture value will be shared for # all tests requesting it ... ``` ``` function: the default scope, the fixture is destroyed at the end of the test. class: the fixture is destroyed during teardown of the last test in the class. module: the fixture is destroyed during teardown of the last test in the module. package: the fixture is destroyed during teardown of the last test in the package. session: the fixture is destroyed at the end of the test session. ``` #### Dynamic scope ``` def determine_scope(fixture_name, config): if config.getoption("--keep-containers", None): return "session" return "function" @pytest.fixture(scope=determine_scope) def docker_container(): yield spawn_container() ``` #### Teardown/Cleanup (AKA Fixture finalization) ##### yield fixtures (recommended) 用yield替代return 測試中碰到yield會返回值並繼續測試 當測試結束,會回去yeild後面執行完fixture > the last fixture to run during setup,the first to run during teardown. > If a yield fixture raises an exception before yielding, pytest won’t try to run the teardown code after that yield fixture’s yield statement. ##### Adding finalizers directly longer use yield don't use this #### Safe teardowns #### Safe fixture structure 真實頁面測試(再打開瀏覽器前啟動fixture,在結束測試後清除) 原子操作->哪個先執行並不會影響結果(一個掛了就都掛了) #### Running multiple assert statements safely autouse #### Fixtures can introspect the requesting test context #### Using markers to pass data to fixtures #### Factories as fixtures ``` @pytest.fixture def make_customer_record(): def _make_customer_record(name): return {"name": name, "orders": []} return _make_customer_record def test_customer_records(make_customer_record): customer_1 = make_customer_record("Lisa") customer_2 = make_customer_record("Mike") customer_3 = make_customer_record("Meredith") ``` #### Parametrizing fixtures ->用不同參數多次跑fixture 主要變化是paramswith 的聲明@pytest.fixture,一個值的列表,fixture 函數將執行每個值,並且可以通過 訪問一個值request.param。無需更改測試功能代碼。 #### Using marks with parametrized fixtures #### Modularity: using fixtures from a fixture function #### Automatic grouping of tests by fixture instances pytest 在測試運行期間最大限度地減少活動夾具的數量。如果您有一個參數化的夾具,那麼使用它的所有測試將首先使用一個實例執行,然後在創建下一個夾具實例之前調用終結器。除此之外,這簡化了對創建和使用全局狀態的應用程序的測試。 #### Use fixtures in classes and modules with usefixtures 運用其他目錄或檔案的fixture ``` @pytest.mark.usefixtures("cleandir") ``` #### Overriding fixtures on various levels #### Override a fixture - test module level - direct test parametrization - non-parametrized one and vice versa #### Using fixtures from other projects All you need to do is to define pytest_plugins in app/tests/conftest.py pointing to that module. ``` pytest_plugins = "mylibrary.fixtures" ``` This effectively registers mylibrary.fixtures as a plugin, making all its fixtures and hooks available to tests in app/tests. ### How to mark test functions with attributes (標記) 通過使用pytest.mark幫助程序,您可以輕鬆地在測試函數上設置元數據。您可以在API Reference中找到內置標記的完整列表。或者,您可以使用 CLI - 列出所有標記,包括內置標記和自定義標記。pytest --markers 內置標記: usefixtures - 在測試函數或類上使用fixtures filterwarnings - 過濾測試功能的某些警告 skip - 總是跳過一個測試函數 skipif - 如果滿足某個條件,則跳過測試函數 xfail - 如果滿足某個條件,則產生“預期失敗”結果 parametrize - 對同一個測試函數執行多次調用。 創建自定義標記或將標記應用於整個測試類或模塊很容易。這些標記可以被插件使用,並且通常用於在命令行上通過選項選擇測試-m。 ### How to parametrize fixtures and test functions pytest 在多個級別啟用測試參數化: pytest.fixture()允許參數化夾具功能。 @pytest.mark.parametrize允許在測試函數或類中定義多組參數和夾具。 pytest_generate_tests允許定義自定義參數化方案或擴展。 多個參數化參數的所有組合,您可以堆疊 parametrize裝飾器: ``` import pytest @pytest.mark.parametrize("x", [0, 1]) @pytest.mark.parametrize("y", [2, 3]) def test_foo(x, y): pass ``` #### 基本pytest_generate_tests示例 有時您可能想要實現自己的參數化方案或實現一些動態來確定夾具的參數或範圍。為此,您可以使用pytest_generate_tests在收集測試函數時調用的鉤子。通過傳入的 metafunc對象,您可以檢查請求的測試上下文,最重要的是,您可以調用metafunc.parametrize()以進行參數化。 例如,假設我們要運行一個測試,接受我們想通過一個新的pytest命令行選項設置的字符串輸入。讓我們首先編寫一個接受stringinput夾具函數參數的簡單測試: ``` # content of test_strings.py def test_valid_string(stringinput): assert stringinput.isalpha() ``` ``` # content of conftest.py def pytest_addoption(parser): parser.addoption( "--stringinput", action="append", default=[], help="list of stringinputs to pass to test functions", ) def pytest_generate_tests(metafunc): if "stringinput" in metafunc.fixturenames: metafunc.parametrize("stringinput", metafunc.config.getoption("stringinput")) ``` ### How to use temporary directories and files in tests tmp_path fixture ``` # content of test_tmp_path.py CONTENT = "content" def test_create_file(tmp_path): d = tmp_path / "sub" d.mkdir() p = d / "hello.txt" p.write_text(CONTENT) assert p.read_text() == CONTENT assert len(list(tmp_path.iterdir())) == 1 assert 0 ``` #### The tmp_path_factory fixture tmp_path_factory夾具 _ 這tmp_path_factory是一個會話範圍的夾具,可用於從任何其他夾具或測試創建任意臨時目錄。 例如,假設您的測試套件需要在磁盤上使用程序生成的大圖像。tmp_path您可以在每個會話中生成一次以節省時間,而不是為每個使用它的測試計算相同的圖像 #### tmpdir and tmpdir_factory fixtures The tmpdir and tmpdir_factory fixtures are similar to tmp_path and tmp_path_factory, but use/return legacy py.path.local objects rather than standard pathlib.Path objects. These days, prefer to use tmp_path and tmp_path_factory. #### 默認基本臨時目錄 臨時目錄默認創建為系統臨時目錄的子目錄。基本名稱將是每次測試運行時遞增的pytest-NUM位置 。NUM此外,超過 3 個臨時目錄的條目將被刪除。 當前無法更改條目數,但使用該--basetemp 選項將在每次運行之前刪除目錄,這實際上意味著僅保留最近運行的臨時目錄。 您可以像這樣覆蓋默認的臨時目錄設置: ``` pytest --basetemp=mydir ``` ### How to monkeypatch/mock modules and environments 有時測試需要調用依賴於全局設置的功能或調用不容易測試的代碼,例如網絡訪問。該monkeypatch夾具可幫助您安全地設置/刪除屬性、字典項或環境變量,或修改sys.path以進行導入。 monkeypatch夾具提供了這些幫助方法,用於在測試中安全地修補和模擬功能 ``` monkeypatch.setattr(obj, name, value, raising=True) monkeypatch.setattr("somemodule.obj.name", value, raising=True) monkeypatch.delattr(obj, name, raising=True) monkeypatch.setitem(mapping, name, value) monkeypatch.delitem(obj, name, raising=True) monkeypatch.setenv(name, value, prepend=None) monkeypatch.delenv(name, raising=True) monkeypatch.syspath_prepend(path) monkeypatch.chdir(path) ``` 在請求的測試功能或夾具完成後,所有修改都將被撤消。 如果設置/刪除操作的目標不存在,該raising 參數確定是否會引發KeyErroror 。AttributeError ### How to run doctests 如何運行文檔測試 默認情況下,所有匹配test*.txt模式的文件都將通過 python 標準doctest模塊運行。您可以通過發出以下命令更改模式: ``` pytest --doctest-glob="*.rst" ``` 在命令行上。--doctest-glob可以在命令行中多次給出。 默認情況下,pytest 將收集test*.txt尋找 doctest 指令的文件,但您可以使用--doctest-glob選項(多允許)傳遞其他 glob。 除了文本文件,您還可以直接從類和函數的文檔字符串執行文檔測試,包括從測試模塊: ``` # content of mymodule.py def something(): """a doctest in a docstring >>> something() 42 """ return 42 ``` ``` $ pytest --doctest-modules =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items mymodule.py . [ 50%] test_example.txt . [100%] ============================ 2 passed in 0.12s ============================= ``` 您可以通過將這些更改放入 pytest.ini 文件中來使這些更改在您的項目中永久生效,如下所示: ``` # content of pytest.ini [pytest] addopts = --doctest-modules ``` ### How to re-run failed tests and maintain state between test runs 該插件提供了兩個命令行選項來重新運行上次pytest調用的失敗: --lf, --last-failed- 只重新運行失敗。 --ff, --failed-first- 先運行故障,然後運行其餘測試。 對於清理(通常不需要),一個--cache-clear選項允許在測試運行之前刪除所有跨會話緩存內容。 其他插件可以訪問config.cache對像以在調用之間設置/獲取 json 可編碼值。pytest ## 進度: 粗讀完 fixture 接續:https://docs.pytest.org/en/7.1.x/how-to/mark.html