Skip to content

Latest commit

 

History

History
282 lines (193 loc) · 21.3 KB

pytest.md

File metadata and controls

282 lines (193 loc) · 21.3 KB

pytest

  • pytest: helps you write better programs — pytest documentation https://docs.pytest.org/en/latest/ 是個 test framework,可以寫簡單的 small test,也可以寫複雜的 functional test #ril

pytest 或 py.test ??

  • 專案、工具名稱都叫 pytest,PR #1633 改以 pytest 做為新的 entry point。

參考資料:

應用實例 {: #powered-by }

為什麼要用 pytest? 測試跟它綁太緊好嗎? 要捨棄 unittest??

  • 許多用了 pytest 的知名專案,用 git grep pytest | wc -l 會發現用量還滿大的,同時 git grep unittest 還真的沒有結果;包括 Requests、Flask 等。

  • pytest fixtures: explicit, modular, scalable — pytest documentation https://docs.pytest.org/en/latest/fixture.html 許多地方要用 @pytest,從此 test code 就跟 pytest 綁定了,對其他人也增加了 pytest 的學習成本?

  • pytest vs nose2 | LibHunt https://python.libhunt.com/project/pytest/vs/nose2 從 fork 數跟 Google Trends,pytest 根本是大勝 nose2

  • Switching from nose to py.test at Mozilla (2015-06-02) https://agopian.info/presentations/2015_06_djangocon_europe/ pytest 的作者同時也參與 tox、pypy 的開發,支援 mark (像 Java 的 annotation,可以做 test filtering)、fixture (透過 test method 的 parameter 注入)、parametrized test;直接用 assertself.assertEqual 更為 pythonic。組態檔分為 pytest.ini (mark、default prarameter) 與 conftest.py (fixture, plugin);pytest + tox + travis 的組合很強大...

  • Why I use py.test and you probably should too (2013-07) http://halfcooked.com/presentations/pyconau2013/why_I_use_pytest.html #ril

新手上路 {: #getting-started }

CLI (pytest) ??

Configuration ??

  • Configuration — pytest documentation #ril
  • pytest -h 看到 "[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found",難怪之前看到有些設定沒有寫在 pytest.ini 也可以作用,從 tox.ini 可見 pytest + tox 是多麼常見的組合。

Live Log ??

skip, xfail, xpass ??

  • Skip and xfail: dealing with tests that cannot succeed — pytest documentation #ril

    • You can mark test functions that cannot be run on CERTAIN PLATFORMS or that you expect to fail so pytest can deal with them accordingly and present a summary of the test session, while keeping the test suite green.

      其中 certain platforms 是相對陝義的說法,下面 "some conditions are met" 的說法比較適當。

    • A skip means that you expect your test to pass ONLY IF SOME CONDITIONS ARE MET, otherwise pytest should skip running the test altogether. Common examples are skipping windows-only tests on non-windows platforms, or skipping tests that depend on an external resource which is not available at the moment (for example a database).

      skip 是指符合某些條件時才執行測試 (通常根據 Python 或 OS version),例如 windows-only test 在 non-windows 平台就會被 skip 掉;是完全不執行,而非 skip 結果。

    • A xfail means that you EXPECT A TEST TO FAIL FOR SOME REASON.

      A common example is a test for a FEATURE NOT YET IMPLEMENTED, or a BUG NOT YET FIXED. When a test passes despite being expected to fail (marked with pytest.mark.xfail), it’s an xpass and will be reported in the test summary.

      xfail (pytest.mark.xfail) 是指你預期測試 "應該" 因為某種原因而失敗 (expected to fail),所以測試通過了反而有問題,會被視為 xpass (unexpectedly passing),例如功能尚未實作、問題當未修正等。

      上述 "feature not yet implemented" 與 "bug not yet fixed" 的說法,意謂著開發過程中可以善用 xfail 做為 TODO,也比較容易看出是否改壞了哪些應該通過的測試。

      不過 xpass 預設也只會 "reported in the test summary" 而已,並不會讓測試錯誤;可以透過 xfail_strict=true (或 -o xfail_strict=true) 強制讓它錯誤。

    • pytest counts and lists skip and xfail tests SEPARATELY. Detailed information about skipped/xfailed tests is not shown by default to avoid cluttering the output. You can use the -r option to see details corresponding to the “short” letters shown in the test progress:

      pytest -rxXs  # show extra info on xfailed, xpassed, and skipped tests
      

      More details on the -r option can be found by running pytest -h.

  • Reference — pytest documentation #ril

區分 Python 2 & 3 的測試 ??

import sys, pytest

py2_only = pytest.mark.skipif(sys.version_info[0] >= 3, reason='Python 2')
py3_later = pytest.mark.skipif(sys.version_info[0] <= 2, reason='Python 3+')

@py2_only
def test_xxx_py2():
    ...

@py3_later
def test_xxx_py3:
    ...
@pytest.fixture(scope="session")
def py2():
    return sys.version_info[0] == 2

Parameterized Tests ??

參考資料:

  • Parametrizing fixtures and test functions — pytest documentation #ril
    • pytest 可以在不同層級實現 test parametrization (同 parameterization 參數化) -- @pytest.fixture() 用在 fixture function,@pytest.mark.parametrize() 用在 test function/class,而 pytest_generate_tests() 則可以用來自訂 test parametrization??
    • @pytest.mark.parametrize() decorator 可以對 test function 的 arguments 進行參數化,例如: 注意第一個參數是 arguments 列表 (不是 list of str),第二個參數是 list of tuple,依序代入不同的 pair of values。
import pytest
@pytest.mark.parametrize("test_input,expected", [
    ("3+5", 8),
    ("2+4", 6),
    ("6*9", 42),
])
def test_eval(test_input, expected):
    assert eval(test_input) == expected
  - 其中 pair of values 可以透過 `pytest.param(values, ..., marks=...)` 加上 marker,例如: `("6*9", 42)` 的組合會被視為 xfail
import pytest
@pytest.mark.parametrize("test_input,expected", [
    ("3+5", 8),
    ("2+4", 6),
    pytest.param("6*9", 42,
                 marks=pytest.mark.xfail),
])
def test_eval(test_input, expected):
    assert eval(test_input) == expected
  - 使用多個 `@pytest.mark.parametrize()` 會有 matrix 的效果。下面的例子會產生 `[0, 1]` x `[2, 3]` = 4 種組合。
import pytest
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
    pass

Collection

  • 預設以當前的目錄往下找,只看 test_**_test.py;這一點和 unittest 的 test discovery 會看 test*.py 有些不同。
  • Module 裡 class 外的 function 會看 test*(),注意 test 後不一定要有底線。
  • Class 裡的 method 規則一樣,不過 class 若不是繼承 unittest.TestCase 的話,就只看名稱以 Test 開頭的 class (為什麼不是以 Test 結尾??)
  • 可以用 --collect-only 來看 test discovery 會找到哪些 test case,但不會真的執行測試。
  • pytest -hcollection: 列出所有跟 test collection 相關的參數。

參考資料:

Plugins??

Code Coverage??

pytest-cov,背後是接 coverage.py

Marker??

pytest 不習慣把 test method 寫在 class 裡??

如何設定受測程式碼的位置??

後來發現 'No module named XXX' 的錯誤,跟 tests/__init__.py 是不存在有很大的關係。

參考資料:

pytest 會自動產生 __pycache__.cache??

對 Functional Test 提供哪些支援??

如何安排 test code 的目錄結構??

如何產生 JUnit XML Report??

如何搭配 tox 使用??

安裝設置 {: #setup }

  • virtualenv pytest 建立一個虛擬環境,然後在環境內安裝 pytest 套件,再用 symbolic link 將環境內 bin/pytest 引出來即可。
  • 但如何引用開發環境 virtualenv 的其他相依性、受測的 code??

參考資料:

參考資料 {: #reference }

社群:

更多:

相關:

手冊: