實際工作中,測試用例的執行可能會依賴於一些外部條件,例如:只能運行在某個特定的操作系統(Windows
),或者我們本身期望它們測試失敗,例如:被某個已知的Bug
所阻塞;如果我們能爲這些用例提前打上標記,那麼pytest
就相應地預處理它們,並提供一個更加準確的測試報告;
在這種場景下,常用的標記有:
skip
:只有當某些條件得到滿足時,才執行測試用例,否則跳過整個測試用例的執行;例如,在非Windows
平臺上跳過只支持Windows
系統的用例;xfail
:因爲一個確切的原因,我們知道這個用例會失敗;例如,對某個未實現的功能的測試,或者阻塞於某個已知Bug
的測試;
pytest
默認不顯示skip
和xfail
用例的詳細信息,我們可以通過-r
選項來自定義這種行爲;
通常,我們使用一個字母作爲一種類型的代表,具體的規則如下:
(f)ailed, (E)rror, (s)kipped, (x)failed, (X)passed, (p)assed, (P)assed with output, (a)ll except passed(p/P), or (A)ll
例如,顯示結果爲XFAIL
、XPASS
和SKIPPED
的用例:
pytest -rxXs
更多細節可以參考:2、使用和調用 – 總結報告
1. 跳過測試用例的執行
1.1. @pytest.mark.skip
裝飾器
跳過執行某個用例最簡單的方式就是使用@pytest.mark.skip
裝飾器,並且可以設置一個可選參數reason
,表明跳過的原因;
@pytest.mark.skip(reason="no way of currently testing this")
def test_the_unknown():
...
1.2. pytest.skip
方法
如果我們想在測試執行期間(也可以在SetUp/TearDown
期間)強制跳過後續的步驟,可以考慮pytest.skip()
方法,它同樣可以設置一個參數msg
,表明跳過的原因;
def test_function():
if not valid_config():
pytest.skip("unsupported configuration")
另外,我們還可以爲其設置一個布爾型的參數allow_module_level
(默認是False
),表明是否允許在模塊中調用這個方法,如果置爲True
,則跳過模塊中剩餘的部分;
例如,在Windows
平臺下,不測試這個模塊:
import sys
import pytest
if not sys.platform.startswith("win"):
pytest.skip("skipping windows-only tests", allow_module_level=True)
注意:
當在用例中設置
allow_module_level
參數時,並不會生效;def test_one(): pytest.skip("跳出", allow_module_level=True) def test_two(): assert 1
也就是說,在上述示例中,並不會跳過
test_two
用例;
1.3. @pytest.mark.skipif
裝飾器
如果我們想有條件的跳過某些測試用例的執行,可以使用@pytest.mark.skipif
裝飾器;
例如,當python
的版本小於3.6
時,跳過用例:
import sys
@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher")
def test_function():
...
我們也可以在兩個模塊之間共享pytest.mark.skipif
標記;
例如,我們在test_module.py
中定義了minversion
,表明當python
的最低支持版本:
# src/chapter-10/test_module.py
import sys
import pytest
minversion = pytest.mark.skipif(sys.version_info < (3, 8),
reason='請使用 python 3.8 或者更高的版本。')
@minversion
def test_one():
assert True
並且,在test_other_module.py
中引入了minversion
:
# src/chapter-10/test_other_module.py
from test_module import minversion
@minversion
def test_two():
assert True
現在,我們來執行這兩個用例(當前虛擬環境的python
版本爲3.7.3
):
λ pipenv run pytest -rs -k 'module' src/chapter-10/
================================ test session starts =================================
platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0
rootdir: D:\Personal Files\Projects\pytest-chinese-doc
collected 2 items
src\chapter-10\test_module.py s [ 50%]
src\chapter-10\test_other_module.py s [100%]
============================== short test summary info ===============================
SKIPPED [1] src\chapter-10\test_module.py:29: 請使用 python 3.8 或者更高的版本。
SKIPPED [1] src\chapter-10\test_other_module.py:26: 請使用 python 3.8 或者更高的版本。
================================= 2 skipped in 0.03s =================================
可以看到,minversion
在兩個測試模塊中都生效了;
因此,在大型的測試項目中,可以在一個文件中定義所有的執行條件,需要時再引入到模塊中;
另外,需要注意的是,當一個用例指定了多個skipif
條件時,只需滿足其中一個,就可以跳過這個用例的執行;
注意:不存在
pytest.skipif()
的方法;
1.4. pytest.importorskip
方法
當引入某個模塊失敗時,我們同樣可以跳過後續部分的執行;
docutils = pytest.importorskip("docutils")
我們也可以爲其指定一個最低滿足要求的版本,判斷的依據是檢查引入模塊的__version__
屬性:
docutils = pytest.importorskip("docutils", minversion="0.3")
我們還可以再爲其指定一個reason
參數,表明跳過的原因;
我們注意到
pytest.importorskip
和pytest.skip(allow_module_level=True)
都可以在模塊的引入階段跳過剩餘部分;實際上,在源碼中它們拋出的都是同樣的異常:# pytest.skip(allow_module_level=True) raise Skipped(msg=msg, allow_module_level=allow_module_level)
# pytest.importorskip() raise Skipped(reason, allow_module_level=True) from None
只是
importorskip
額外增加了minversion
參數:# _pytest/outcomes.py if minversion is None: return mod verattr = getattr(mod, "__version__", None) if minversion is not None: if verattr is None or Version(verattr) < Version(minversion): raise Skipped( "module %r has __version__ %r, required is: %r" % (modname, verattr, minversion), allow_module_level=True, )
從中我們也證實了,它實際檢查的是模塊的
__version__
屬性;所以,對於一般場景下,使用下面的方法可以實現同樣的效果:
try: import docutils except ImportError: pytest.skip("could not import 'docutils': No module named 'docutils'", allow_module_level=True)
1.5. 跳過測試類
在類上應用@pytest.mark.skip
或@pytest.mark.skipif
:
# src/chapter-10/test_skip_class.py
import pytest
@pytest.mark.skip("作用於類中的每一個用例,所以 pytest 共收集到兩個 SKIPPED 的用例。")
class TestMyClass():
def test_one(self):
assert True
def test_two(self):
assert True
1.6. 跳過測試模塊
在模塊中定義pytestmark
變量(推薦):
# src/chapter-10/test_skip_module.py
import pytest
pytestmark = pytest.mark.skip('作用於模塊中的每一個用例,所以 pytest 共收集到兩個 SKIPPED 的用例。')
def test_one():
assert True
def test_two():
assert True
或者,在模塊中調用pytest.skip
方法,並設置allow_module_level=True
:
# src/chapter-10/test_skip_module.py
import pytest
pytest.skip('在用例收集階段就已經跳出了,所以不會收集到任何用例。', allow_module_level=True)
def test_one():
assert True
def test_two():
assert True
1.7. 跳過指定文件或目錄
通過在conftest.py
中配置collect_ignore_glob
項,可以在用例的收集階段跳過指定的文件和目錄;
例如,跳過當前測試目錄中文件名匹配test_*.py
規則的文件和config
的子文件夾sub
中的文件:
collect_ignore_glob = ['test*.py', 'config/sub']
更多細節可以參考:https://docs.pytest.org/en/5.1.3/example/pythoncollection.html#customizing-test-collection
1.8. 總結
pytest.mark.skip |
pytest.mark.skipif |
pytest.skip |
pytest.importorskip |
conftest.py |
|
---|---|---|---|---|---|
用例 | @pytest.mark.skip() |
@pytest.mark.skipif() |
pytest.skip(msg='') |
/ | / |
類 | @pytest.mark.skip() |
@pytest.mark.skipif() |
/ | / | / |
模塊 | pytestmark = pytest.mark.skip() |
pytestmark = pytest.mark.skipif() |
pytest.skip(allow_module_level=True) |
pytestmark = pytest.importorskip() |
/ |
文件或目錄 | / | / | / | / | collect_ignore_glob |
2. 標記用例爲預期失敗的
我們可以使用@pytest.mark.xfail
標記用例,表示期望這個用例執行失敗;
用例會正常執行,只是失敗時不再顯示堆棧信息,最終的結果有兩個:用例執行失敗時(XFAIL
:符合預期的失敗)、用例執行成功時(XPASS
:不符合預期的成功)
另外,我們也可以通過pytest.xfail
方法在用例執行過程中直接標記用例結果爲XFAIL
,並跳過剩餘的部分:
def test_function():
if not valid_config():
pytest.xfail("failing configuration (but should work)")
同樣可以爲pytest.xfail
指定一個reason
參數,表明原因;
下面我們來重點看一下@pytest.mark.xfail
的用法:
-
condition
位置參數,默認值爲None
和
@pytest.mark.skipif
一樣,它也可以接收一個python
表達式,表明只有滿足條件時才標記用例;例如,只在
pytest 3.6
版本以上標記用例:@pytest.mark.xfail(sys.version_info >= (3, 6), reason="python3.6 api changes") def test_function(): ...
-
reason
關鍵字參數,默認值爲None
可以指定一個字符串,表明標記用例的原因;
-
strict
關鍵字參數,默認值爲False
當
strict=False
時,如果用例執行失敗,結果標記爲XFAIL
,表示符合預期的失敗;如果用例執行成功,結果標記爲XPASS
,表示不符合預期的成功;當
strict=True
時,如果用例執行成功,結果將標記爲FAILED
,而不再是XPASS
了;我們也可以在
pytest.ini
文件中配置:[pytest] xfail_strict=true
-
raises
關鍵字參數,默認值爲None
可以指定爲一個異常類或者多個異常類的元組,表明我們期望用例上報指定的異常;
如果用例的失敗不是因爲所期望的異常導致的,
pytest
將會把測試結果標記爲FAILED
; -
run
關鍵字參數,默認值爲True
:當
run=False
時,pytest
不會再執行測試用例,直接將結果標記爲XFAIL
;
我們以下表來總結不同參數組合對測試結果的影響(其中xfail = pytest.mark.xfail
):
@xfail() |
@xfail(strict=True) |
@xfail(raises=IndexError) |
@xfail(strict=True, raises=IndexError) |
@xfail(..., run=False) |
|
---|---|---|---|---|---|
用例測試成功 | XPASS |
FAILED |
XPASS |
FAILED |
XFAIL |
用例測試失敗,上報AssertionError |
XFAIL |
XFAIL |
FAILED |
FAILED |
XFAIL |
用例上報IndexError |
XFAIL |
XFAIL |
XFAIL |
XFAIL |
XFAIL |
2.1. 去使能xfail
標記
我們可以通過命令行選項pytest --runxfail
來去使能xfail
標記,使這些用例變成正常執行的用例,彷彿沒有被標記過一樣:
同樣,pytest.xfail()
方法也將會失效;
3. 結合pytest.param
方法
pytest.param
方法可用於爲@pytest.mark.parametrize
或者參數化的fixture
指定一個具體的實參,它有一個關鍵字參數marks
,可以接收一個或一組標記,用於標記這輪測試的用例;
我們以下面的例子來說明:
# src/chapter-10/test_params.py
import pytest
import sys
@pytest.mark.parametrize(
('n', 'expected'),
[(2, 1),
pytest.param(2, 1, marks=pytest.mark.xfail(), id='XPASS'),
pytest.param(0, 1, marks=pytest.mark.xfail(raises=ZeroDivisionError), id='XFAIL'),
pytest.param(1, 2, marks=pytest.mark.skip(reason='無效的參數,跳過執行')),
pytest.param(1, 2, marks=pytest.mark.skipif(sys.version_info <= (3, 8), reason='請使用3.8及以上版本的python。'))])
def test_params(n, expected):
assert 2 / n == expected
執行:
λ pipenv run pytest -rA src/chapter-10/test_params.py
================================ test session starts =================================
platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0
rootdir: D:\Personal Files\Projects\pytest-chinese-doc
collected 5 items
src\chapter-10\test_params.py .Xxss [100%]
======================================= PASSES =======================================
============================== short test summary info ===============================
PASSED src/chapter-10/test_params.py::test_params[2-1]
SKIPPED [1] src\chapter-10\test_params.py:26: 無效的參數,跳過執行
SKIPPED [1] src\chapter-10\test_params.py:26: 請使用3.8及以上版本的python。
XFAIL src/chapter-10/test_params.py::test_params[XFAIL]
XPASS src/chapter-10/test_params.py::test_params[XPASS]
================= 1 passed, 2 skipped, 1 xfailed, 1 xpassed in 0.08s =================
關於參數化的fixture
的細節可以參考:4、fixtures:明確的、模塊化的和可擴展的 – 在參數化的fixture中標記用例