pytest封神之路第四步 內建和自定義marker

dongfanger 發表於 2020-09-22

可以通過命令列檢視所有marker,包括內建和自定義的

pytest --markers

內建marker

內建marker本文先講usefixtures 、filterwarnings 、skip 、skipif 、xfail這5個。引數化的marker我會寫在《pytest引數化》中,hook的marker我會寫在《pytest hook》中,外掛的marker(pytest-ordering、allure等)我會寫在《pytest外掛》中。當前只需知道有以上這些分類的marker即可,更多內容請關注後續文章。

usefixtures

如果我們只想把fixture注入到test中,test不直接訪問fixture的時候,就需要用到usefixtures。

示例,test需要一個臨時目錄,但是並不需要知道這個目錄具體路徑在哪

# content of conftest.py
import os
import shutil
import tempfile

import pytest


@pytest.fixture
def cleandir():
    old_cwd = os.getcwd()
    newpath = tempfile.mkdtemp()
    os.chdir(newpath)
    yield
    os.chdir(old_cwd)
    shutil.rmtree(newpath)

# content of test_setenv.py
import os

import pytest


@pytest.mark.usefixtures("cleandir")
class TestDirectoryInit:
    def test_cwd_starts_empty(self):
        assert os.listdir(os.getcwd()) == []

    with open("myfile", "w") as f:
        f.write("hello")

    def test_cwd_again_starts_empty(self):
        assert os.listdir(os.getcwd()) == []

TestDirectoryInit的測試方法需要一個臨時目錄作為當前工作目錄,在類上新增@pytest.mark.usefixtures("cleandir"),類的方法不加fixture也能有"cleandir"的效果。

usefixtures可以新增多個fixture

@pytest.mark.usefixtures("cleandir", "anotherfixture")

usefixtures可以用在pytestmark,作用域是定義所在module的所有tests

pytestmark = pytest.mark.usefixtures("cleandir")

usefixtures也可以用在pytest.ini,作用域是整個專案的所有tests

# content of pytest.ini
[pytest]
usefixtures = cleandir

不過需要注意的是fixture函式本身是不能用usefixtures的,如果想要巢狀fixture,只能通過在fixture修飾的函式中,新增引數這種方式。

filterwarnings

過濾警告資訊。

示例,api_v1()丟擲了“api v1”的警告,test_one()函式使用filterwarnings過濾掉了

import warnings


def api_v1():
    warnings.warn(UserWarning("api v1, should use functions from v2"))
    return 1


@pytest.mark.filterwarnings("ignore:api v1")
def test_one():
    assert api_v1() == 1

同樣可以新增到pytestmark和pytest.ini中。

skip

跳過,不測試。

示例,skip需要新增reason哦

@pytest.mark.skip(reason="no way of currently testing this")
def test_the_unknown():
    ...

不過,更實用的方式是呼叫pytest.skip(reason)函式,而不是用mark,這樣就可以用if判斷跳不跳

def test_function():
    if not valid_config():
        pytest.skip("unsupported configuration")

allow_module_level 可以跳過整個module

import sys
import pytest

if not sys.platform.startswith("win"):
    pytest.skip("skipping windows-only tests", allow_module_level=True)

skipif

if判斷跳不跳,還可以用skipif。

示例,如果Python版本小於3.6就跳過測試

import sys

@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher")
def test_function():
    ...

如果想在summary中看到reason,需要新增-rs引數。

可以把skipif賦值給變數,然後直接引用變數,或者把變數import到其他module中使用

# content of test_mymodule.py
import mymodule

minversion = pytest.mark.skipif(
    mymodule.__versioninfo__ < (1, 1), reason="at least mymodule-1.1 required"
)


@minversion
def test_function():
    ...

# test_myothermodule.py
from test_mymodule import minversion

@minversion
def test_anotherfunction():
    ...

skipif新增到class上,會跳過類中所有方法。

可以使用pytestmark跳過module內所有test

# test_module.py
pytestmark = pytest.mark.skipif(...)

如果function有多個skipif作用,只要有一個為True,就會跳過。

xfail

明知失敗,依然前行!不好意思跑偏了。xfail就是expected fail,預期失敗

@pytest.mark.xfail
def test_function():
    ...

執行後summary不會統計為"failed",會單獨列出來。如果結果失敗了,“expected to fail” (XFAIL);如果結果成功了,“unexpectedly passing” (XPASS)。但是整個執行結果是”Tests passed“。

if判斷

def test_function():
    if not valid_config():
        pytest.xfail("failing configuration (but should work)")

值得注意的是,marker會繼續執行所有test程式碼,pytest.xfail()函式會丟擲異常,中斷執行後續程式碼

新增condition,判斷條件

@pytest.mark.xfail(sys.platform == "win32", reason="bug in a 3rd party library")
def test_function():
    ...

新增reason,理由

@pytest.mark.xfail(reason="known parser issue")
def test_function():
    ...

新增raises,丟擲異常/錯誤

@pytest.mark.xfail(raises=RuntimeError)
def test_function():
    ...

新增run,禁止執行

@pytest.mark.xfail(run=False)
def test_function():
    ...

新增strict,嚴格模式,即使xpass也會強制失敗,summary中有輸出資訊”[XPASS(strict)] “,測試結果為”Tests failed“。

@pytest.mark.xfail(strict=True)
def test_function():
    ...

斷言成功也強制失敗,確實夠強勢的!

可以在ini檔案中定義全域性strict

[pytest]
xfail_strict=true

在命令列新增--runxfail,忽略xfail marker,相當於沒有新增這個標記的效果,該成功就成功,該失敗就失敗,再強勢也不虛,哈哈,惡靈退散。

pytest --runxfail
pytest --runxfail

再強勢也不虛,惡靈退散,哈哈。

自定義marker

通過註解自定義marker

# content of test_server.py
import pytest


@pytest.mark.webtest
def test_send_http():
    pass  # perform some webtest test for your app


def test_something_quick():
    pass


def test_another():
    pass


class TestClass:
    def test_method(self):
        pass

在命令列通過-m指定執行mark打標的test

$ pytest -v -m webtest

也可以反選

$ pytest -v -m "not webtest"

但是,這樣定義的marker是未註冊的!在執行後會警告,PytestUnknownMarkWarning。如果新增了命令列引數--strict-markers ,未註冊的marker會報錯

可以在pytest.ini檔案中註冊,冒號後面的所有程式碼都是marker說明,包括換行

[pytest]
markers =
    slow: marks tests as slow (deselect with '-m "not slow"')
    serial

更高階的,可以在pytest_configure hook函式中註冊,這主要用在第三方外掛

def pytest_configure(config):
    config.addinivalue_line(
        "markers", "env(name): mark test to run only on named environment"
    )

簡要回顧

本文介紹了5個pytest內建的marker,接著介紹瞭如何自定義marker和註冊marker。通過marker,可以讓我們更靈活的執行用例。

參考資料

docs-pytest-org-en-stable