zl程序教程

您现在的位置是:首页 >  后端

当前栏目

自动化测试框架Pytest使用mark和参数化固定装置、测试函数

pytest测试框架自动化 参数 mark 使用
2023-09-27 14:26:35 时间

目录

一、常见的内置markers

二、查看所有markers

三、注册自定义marks

四、对未注册mark的限制

五、 参数化固定装置、测试函数


一、常见的内置markers

usefixtures - 为测试函数或者测试类知名使用那些fixture

filterwarnings - 为一个测试函数过滤一个指定的告警

skip - 跳过一个测试函数

skipif - 如果满足条件就跳过测试函数

xfail - 标记用例失败

parametrize - 参数化

创建自定义标记或将标记应用于整个测试类或模块都很容易。这些标记可以被插件使用,也通常用于在使用-m选项的命令行上选择测试。

注意:mark标记只能应用于被测函数,对固定装置没有影响

二、查看所有markers

如下,可以查看到当前环境中的所有markers

$ pytest --markers
@pytest.mark.forked: Always fork for this test.

@pytest.mark.flaky(reruns=1, reruns_delay=0): mark test to re-run up to 'reruns' times. Add a delay of 'reruns_delay' seconds between re-runs.

@pytest.mark.hypothesis: Tests which use hypothesis.

@pytest.mark.allure_label: allure label marker

@pytest.mark.allure_link: allure link marker

@pytest.mark.allure_description: allure description

@pytest.mark.allure_description_html: allure description html

@pytest.mark.filterwarnings(warning): add a warning filter to the given test. see https://docs.pytest.org/en/stable/warnings.html#pytest-mark-filterwarnings

@pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.

@pytest.mark.skipif(condition, ..., *, reason=...): skip the given test function if any of the conditions evaluate to True. Example: skipif(sys.platform == 'win32') skip
s the test if we are on the win32 platform. See https://docs.pytest.org/en/stable/reference.html#pytest-mark-skipif

@pytest.mark.xfail(condition, ..., *, reason=..., run=True, raises=None, strict=xfail_strict): mark the test function as an expected failure if any of the conditions eva
luate to True. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expe
cted, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See https://docs.pytest.org/en/stable/reference.html#pytes
t-mark-xfail

@pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of valu
es if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls o
f the decorated test function, one with arg1=1 and another with arg1=2.see https://docs.pytest.org/en/stable/parametrize.html for more info and examples.

@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/fixture.html#usefix
tures

@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.

@pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.

三、注册自定义marks

方式一:在pytest.ini中按照如下格式声明即可,冒号之前为注册的mark名称,冒号之后为此mark的说明

[pytest]
markers =
    smoke: smoke tests
    test: system tests

此时test_demo.py代码如下

import pytest

@pytest.mark.smoke
def test_func():
    assert 1==1

@pytest.mark.test
def test_func2():
    assert 1==1

def test_func3():
    assert 1==1

使用pytest -m smoke执行结果如下,发现此时即只执行了标记为smoke的一个用例,这就是和自定义mark的使用方法

$ pytest -m smoke
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 3 items / 2 deselected / 1 selected                                                                                                                           

test_demo.py .                                                                                                                                                    [100%]

=================================================================== 1 passed, 2 deselected in 0.04s ====================================================================


方式二:在conftest.py文件中重写pytest_configure函数即可,比如如下,注册两个mark:smoke和test

def pytest_configure(config):
    config.addinivalue_line(
        "markers", "smoke: smoke test"
    )
    config.addinivalue_line(
        "markers", "test: system test"
    )

test_demo.py代码如下:

import pytest

@pytest.mark.smoke
def test_func():
    assert 1==1

@pytest.mark.test
def test_func2():
    assert 1==1

def test_func3():
    assert 1==1

通过pytest -m test 执行结果如下:

$ pytest -m test
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 3 items / 2 deselected / 1 selected                                                                                                                           

test_demo.py .                                                                                                                                                    [100%]

=================================================================== 1 passed, 2 deselected in 0.02s ====================================================================

四、对未注册mark的限制

默认情况下,对未注册mark直接使用是会产生一条告警信息,比如这里把pytest.ini和conftest.py都删除掉,只剩test_demo.py一个文件

test_demo.py代码如下

import pytest

@pytest.mark.smoke
def test_func():
    assert 1==1

@pytest.mark.test
def test_func2():
    assert 1==1

def test_func3():
    assert 1==1

直接使用 pytest -m smoke 执行结果如下,可以发现这里产生了两条告警,这就是因为这两条告警未在pytest.ini或者conftest.py中进行注册的原因,在实际项目开发中如果在执行测试的时候发现了这种大片告警打印,解决办法就是在pytest.ini或者conftest.py将这些告警报出的mark都进行注册即可

$ pytest -m smoke
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 3 items / 2 deselected / 1 selected                                                                                                                           

test_demo.py .                                                                                                                                                    [100%]

=========================================================================== warnings summary ===========================================================================
test_demo.py:3
  D:\src\blog\tests\test_demo.py:3: PytestUnknownMarkWarning: Unknown pytest.mark.smoke - is this a typo?  You can register custom marks to avoid this warning - for deta
ils, see https://docs.pytest.org/en/stable/mark.html
    @pytest.mark.smoke

test_demo.py:7
  D:\src\blog\tests\test_demo.py:7: PytestUnknownMarkWarning: Unknown pytest.mark.test - is this a typo?  You can register custom marks to avoid this warning - for detai
ls, see https://docs.pytest.org/en/stable/mark.html
    @pytest.mark.test

-- Docs: https://docs.pytest.org/en/stable/warnings.html
============================================================= 1 passed, 2 deselected, 2 warnings in 0.02s ==============================================================

如果希望强制限制必须先注册再使用mark,则可以在pytest.ini中加上如下配置即可

[pytest]
addopts = --strict-markers

比如test_demo.py代码:

import pytest

@pytest.mark.smoke
def test_func():
    assert 1==1

@pytest.mark.test
def test_func2():
    assert 1==1

def test_func3():
    assert 1==1

此时继续使用pytest -m smoke执行结果如下,发现此时已经报错了,即强制限制必须对mark进行注册

$ pytest -m smoke
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 0 items / 1 error                                                                                                                                             

================================================================================ ERRORS ================================================================================
____________________________________________________________________ ERROR collecting test_demo.py _____________________________________________________________________
'smoke' not found in `markers` configuration option
======================================================================= short test summary info ========================================================================
ERROR test_demo.py
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
=========================================================================== 1 error in 0.14s ===========================================================================

五、 参数化固定装置、测试函数

Pytest可以在以下几个级别上实现测试参数化:

  pytest.fixture() 允许 parametrize fixture functions

  @pytest.mark.parametrize 定义多组参数、固定装置在测试函数、类上面

   pytest_generate_tests 允许用户定义自定义参数化方案或扩展。

1.参数化测试函数:@pytest.mark.parametrize

(1) @parametrize 装饰器定义了三个不同的元组

@parametrize 装饰器定义了三个不同的(test_input,expected)元组,以便test_eval函数将依次使用它们运行三次:

# content of test_expectation.py
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
C:\Users\Desktop\python>pytest test_expectation.py
================================================== test session starts ===================================================
platform win32 -- Python 3.8.0, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: C:\Users\X21201\Desktop\python
collected 3 items                                                                                                         
test_expectation.py ..F                                                                                             [100%]
======================================================== FAILURES ========================================================
___________________________________________________ test_eval[6*9-42] ____________________________________________________
test_input = '6*9', expected = 42
    @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
E       AssertionError: assert 54 == 42
E        +  where 54 = eval('6*9')
test_expectation.py:7: AssertionError
================================================ short test summary info =================================================
FAILED test_expectation.py::test_eval[6*9-42] - AssertionError: assert 54 == 42
============================================== 1 failed, 2 passed in 0.07s ===============================================

(2)在类或模块上使用参数化标记

import pytest
@pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
class TestClass:
    def test_simple_case(self, n, expected):
        assert n + 1 == expected
    def test_weird_simple_case(self, n, expected):
        assert (n * 1) + 1 == expected
复制代码
collected 4 items                                                                                                                                                                       
test_expectation.py::TestClass::test_simple_case[1-2] PASSED
test_expectation.py::TestClass::test_simple_case[3-4] PASSED
test_expectation.py::TestClass::test_weird_simple_case[1-2] PASSED
test_expectation.py::TestClass::test_weird_simple_case[3-4] PASSED

(3)参数化一个py文件中所有测试

import pytest
pytestmark = pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
class TestClass:
    def test_simple_case(self, n, expected):
        assert n + 1 == expected
    def test_weird_simple_case(self, n, expected):
        assert (n * 1) + 1 == expected
class TestDemo:
    def test_simple_case_001(self, n, expected):
        assert n + 1 == expected
collected 6 items                                                                                                                                                                       
test_expectation.py::TestClass::test_simple_case[1-2] PASSED
test_expectation.py::TestClass::test_simple_case[3-4] PASSED
test_expectation.py::TestClass::test_weird_simple_case[1-2] PASSED
test_expectation.py::TestClass::test_weird_simple_case[3-4] PASSED
test_expectation.py::TestDemo::test_simple_case_001[1-2] PASSED
test_expectation.py::TestDemo::test_simple_case_001[3-4] PASSED

(4)参数化中标记单个测试实例:使用 mark.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
collected 3 items                                                                                                                                                                                                                             
test_expectation.py::test_eval[3+5-8] PASSED
test_expectation.py::test_eval[2+4-6] PASSED
test_expectation.py::test_eval[6*9-42] XFAIL

(5) 参数的所有组合:堆叠参数化装饰器

import pytest
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
    pass
collected 4 items                                                                                                                                                                     
test_expectation.py::test_foo[2-0] PASSED
test_expectation.py::test_foo[2-1] PASSED
test_expectation.py::test_foo[3-0] PASSED
test_expectation.py::test_foo[3-1] PASSED

2.pytest_generate_tests 示例

有时,您可能想要实现您自己的参数化方案,或实现一些动态来确定一个设备的参数或范围。为此,您可以使用在收集测试函数时调用的pytest_generate_tests钩子

# 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"))
# content of test_strings.py
def test_valid_string(stringinput):
    assert stringinput.isalpha()

执行测试,动态指定参数:

pytest --stringinput="hello" --stringinput="world" test_strings.py -vs
pytest -q -rs test_strings.py

B站最牛的Python自动化测试框架全栈测试开发实战项目入门到精通,涨薪必备教程!!!