Pytest enables test parameterization at the following levels:

  • Pytest.fixture (), which parameterizes the fixture function.
  • Pytest.mark. parametrize, which allows you to define multiple sets of parameters and fixtures in a test function or class.
  • Pytest_generate_tests, which can be customized parameterized schemes or extensions.

Parametrize: @pytest.mark.parametrize: parameterized test function

1. General usage

Function of the test parameters are parameterized, directly using the built-in decorator pytest. Mark. The parameterized.

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
Copy the code

As you can see from the code, there are three different tuples defined in the decorator. We put (” test_input, expected “, [(” 3 + 5 “, 8), (” 2 + 4 “, 6), (” 6 * 9 “, 42)]) apart to see:

  • “Test_input,expected” : This string defines two parameters,test_inputandexpected.
  • (” 3 + 5 “, 8), (” 2 + 4 “, 6), (” 6 * 9 “, 42) : three yuan group here, didn’t have 2 element in a tuple, in sequence, respectivelytest_inputandexpected.
  • [] : list is parameterized specific parameters, because there are three different tuples passed, so test functiontest_evalThree separate executions are performed.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = test session starts = = = = = = = = = = = = = = = = = = = = = = = = = = = = = platform win32 - Python 3.9.4, Pytest-6.2.3, py-1.10.0, pluggy-0.13.1 RootDir: D:\PycharmProjects\ wmS-api \interface, configFile: pytest.inicollected 3 items test_module1.py .. F demo\test_module1.py:3 (test_eval[6*9-42]) 54 ! = 42 Expected :42 Actual :54 <Click to see difference> 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_module1.py:6: AssertionErrorCopy the code

The last test fails because the third test fails because 54 does not equal 42.

2. Mark a single test instance in parameterization

Marking individual test instances in parameterization, such as the previously mentioned mark.xfail, can mark test functions as failures. In parameterization, if you want one of the parameters to fail the test, you can use:

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
Copy the code

Run it:

Test_module1. Py [100%] = = = = = = = = = = = = = = = = = = = = = = = = 2 passed, 1 xfailed in 0.05 s = = = = = = = = = = = = = = = = = = = = = = = = =.. xCopy the code

3. Multiple parameterized combinations, Cartesian product

If multiple parameterized decorators are added to the test function, the resulting combination of parameters is a Cartesian product:

import pytest


@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
    print("\nx:", x)
    print("y:", y)
Copy the code

X =0/y=2, x=1/y=2, x=0/y=3, and x=1/y=3

test_module1.py . x: 0 y: 2 . x: 1 y: 2 . x: 0 y: 3 . x: 1 y: 3 [100%] = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 4 passed in 0.01 s = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =Copy the code

Pytest_generate_tests Example (test_Generate_tests Example)

If there are scenarios where you need to dynamically determine the scope of arguments or fixtures, you can use the Pytest_Generate_tests hook function, which is called when you collect test functions. The context of the requested test function can be checked with the metafunc object passed in, and metafunc.parameterize() can be further called to parameterize.

For example, if you have a test function that needs to take an input string as an argument and gets it from the PyTest command line, you need to write a fixture function that gets the parameters to call the test function.

# content of test_strings.py

def test_valid_string(stringinput):
    assert stringinput.isalpha()
Copy the code

Create a new conftest.py file and write the fixture function here:

# 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"))
Copy the code

Now run the test function on the command line: pytest -q — stringInput =”hello” — stringInput =”world” test_strings.py, which will run twice.

D:\PycharmProjects\wms-api\interface\demo>pytest -q --stringinput="hello" --stringinput="world" test_strings.py
..                                                                                                                                                                     [100%]
2 passed in 0.01s
Copy the code

Pytest -q — stringInput =”!” test_strings.py

FAILED test_strings.py::test_valid_string[!] - AssertionError: assert False
1 failed in 0.04s
Copy the code

If there is no string input, the test function will be skipped. Because when metafunc.parameterize() is called, a list is passed: pytest-q-rs test_strings.py

SKIPPED [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:2
1 skipped in 0.12s
Copy the code

Note that when metafunc is called, if you parameterize multiple times with different parameter sets, all parameter names on these parameter sets cannot be repeated, otherwise an error will be reported.

Conclusion: Of the three usages mentioned in the article, the first is the most common, the second is the second, and the third can be used as some inspiration.