十、pytest -- skip和xfail標記

往期索引:http://www.javashuo.com/article/p-wpvwlyap-bn.htmlpython

實際工做中,測試用例的執行可能會依賴於一些外部條件,例如:只能運行在某個特定的操做系統(Windows),或者咱們自己指望它們測試失敗,例如:被某個已知的Bug所阻塞;若是咱們能爲這些用例提早打上標記,那麼pytest就相應地預處理它們,並提供一個更加準確的測試報告;shell

在這種場景下,經常使用的標記有:windows

  • skip:只有當某些條件獲得知足時,才執行測試用例,不然跳過整個測試用例的執行;例如,在非Windows平臺上跳過只支持Windows系統的用例;
  • xfail:由於一個確切的緣由,咱們知道這個用例會失敗;例如,對某個未實現的功能的測試,或者阻塞於某個已知Bug的測試;

pytest默認不顯示skipxfail用例的詳細信息,咱們能夠經過-r選項來自定義這種行爲;api

一般,咱們使用一個字母做爲一種類型的表明,具體的規則以下:數組

(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

例如,顯示結果爲XFAILXPASSSKIPPED的用例:session

pytest -rxXs

更多細節能夠參考:二、使用和調用 -- 總結報告xss

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.importorskippytest.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的細節能夠參考:四、fixtures:明確的、模塊化的和可擴展的 -- 在參數化的fixture中標記用例

相關文章
相關標籤/搜索