pytest parameterization: a powerful tool to simplify test case writing

Insert image description here

In the actual scenario, we test the simple registration function, which requires a username and password. The username/password may have some rules, which requires a variety of different rules of data to verify the registration function. Of course, we can write multiple cases, the requests are the same but the request data is different. But there is a problem with this, it will cause a lot of duplicate code and is not easy to manage. So how to solve it elegantly? Of course it is parameterization, so how does pytest perform parameterization? Explore together with questions.

Introduction to pytest parameterization

Parameterized testing refers to running multiple tests by passing in different parameters in the test case to verify the different input and output of the function or method under test.

Pytest parameterization allows us to easily expand test cases, reduce redundant code, and improve testing efficiency.

How to use pytest parameterization

The method of use is still very simple. Let’s look at a case first. Maybe you will understand it at a glance.

@pytest.mark.parametrize("input, expected", [
    (2, 4),
    (3, 9),
    (4, 16)
])
def test_square(input, expected):
    assert input**2 == expected

The above code defines a test case named test_square, which is parameterized using the @pytest.mark.parametrize decorator. The parameterized parameter list contains multiple sets of inputs and desired outputs, separated by commas between each set of parameters.

Next we execute the test and see the results:

Run the test by executing the following command on the command line:

============================= test session starts ==============================
collecting ... collected 3 items
​
test_demo.py::test_square[2-4] 
test_demo.py::test_square[3-9] 
test_demo.py::test_square[4-16]============================== 3 passed in 0.13s ===============================

Application scenarios

The usage method is just like the above case, it is very simple. Let’s focus on the application scenario.

Single parametric application

Common usage scenarios: Only one data changes in the test method, that is, multiple sets of test data are passed in through a parameter. During execution, each set of data is executed once.

For example, if we register for verification code, there are currently multiple mobile phone numbers that need to be registered:

import pytest
​
​
@pytest.mark.parametrize("mobile", [
    16300000000,
    16300000001,
    16300000002
])
def test_register(mobile):
    print(f"当前注册手机号为:{mobile}")

The execution results are as follows:

PASSED                          [ 33%]当前注册手机号为:16300000000
PASSED                          [ 66%]当前注册手机号为:16300000001
PASSED                          [100%]当前注册手机号为:16300000002

It can be seen that a test case will be executed multiple times if it has multiple pieces of data.

Multi-parameter application

The input data for the test can be an expression, and the input parameters can be multiple. Multiple data can be organized in tuples.

For example, if we test the calculation function, we can write it like this:

import pytest
​
​
@pytest.mark.parametrize("input, expected", [
    (2+2, 4),
    (10-1, 9),
    (4**2, 16)
])
def test_square(input, expected):
    print(input)
    assert input == expected

This code performs different calculation operations on input values ​​and verifies that the results are as expected.

multiple parameterizations

A use case can be marked using multiple @pytest.mark.parametrizes. For example:

import pytest
​
​
@pytest.mark.parametrize('input', [1, 2, 3])
@pytest.mark.parametrize("output, expected", [
    (4, 5),
    (6, 7)
])
def test_square(input, output, expected):
    print(f"input:{input},output:{output},expected:{expected}")
    result = input + output
    assert result == expected

We use two levels of nested @pytest.mark.parametrize decorators. The outer parameterized decorator specifies the value range of the input parameter as [1, 2, 3], and the inner parameterized decorator specifies each set of values ​​for the output and expected parameters.

The combination of parameterization and fixture

Fixtures can also be parameterized. They are introduced in detail in the previous article. I will not introduce them in this article. Students who don’t know how to do this can read this article.

pytestmark implements parameterization

pytestmark can be used to apply decorators at the test module level or class level. By using pytestmark, we can apply parameterization uniformly for multiple test functions in the test module.

Let’s look at the case:

import pytest
​
pytestmark = pytest.mark.parametrize('input', [1, 2, 3])
​
def test_square(input):
    result = input ** 2
    assert result == 4

In this code, we use the pytest.mark.parametrize decorator in the module-level pytestmark variable and set the input parameter to a parameterized value of [1, 2, 3].

This means that for each test function in the test module, parameterization is applied and the test is executed for each value in [1, 2, 3]. In the test_square test function, we directly use input as a parameter to access the parameterized value, calculate the square of input, and assert that the result is 4.

By using pytestmark, we can easily apply parameterization throughout the test module without repeating the same decorator in every test function. This approach is particularly useful when the same parameterization needs to be applied uniformly to multiple test functions.

It is important to note that when using pytestmark, it will apply parameterization to all test functions in the entire module. If you only want to apply parameterization to a specific test function, you can apply the decorator directly to that function without using the pytestmark variable.

Digging deeper into parametrize

Let’s take a look at the source code first: /_pytest/python.py

def parametrize(
        self,
        argnames: Union[str, Sequence[str]],
        argvalues: Iterable[Union[ParameterSet, Sequence[object], object]],
        indirect: Union[bool, Sequence[str]] = False,
        ids: Optional[
            Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]]
        ] = None,
        scope: "Optional[_ScopeName]" = None,
        *,
        _param_mark: Optional[Mark] = None,
    ) -> None:

argnames: parameter name, which can be a string or a list of strings, representing the parameter names in the test function. If there are multiple parameters, multiple names can be specified via a list.

argvalues: parameter values, which can be an iterable object, where each element represents a set of parameter values. Each set of parameter values ​​can be a tuple, list, or a single object.

indirect: Whether to use the parameter as an indirect parameter of the function under test. If set to True or specify certain parameter names, when the test function is executed, the parameters are passed in as return values ​​from other fixture functions rather than directly as parameter values.

ids: The identifier of the test case, used to better distinguish different parameterized test cases in the test report. You can specify an iterable or a function to generate the identity.

scope: The scope of the parameter, used when sharing parameters between multiple test functions. Can be set to "function" (default), "class", "module" or "session".

_param_mark: Internal parameter, used to specify parameter mark. Generally no need to pay attention.

Multiple sets of different parameterizations can be added to the test function by calling the pytest.parametrize function multiple times, with each call applying the parameterization on top of the previously added parameterization.

Note that the pytest.parametrize function is parameterized during the test case collection phase, rather than dynamically generating parameters each time a test case is executed.

Use the hook function pytest_generate_tests combined with parametrize to dynamically generate test cases.

pytest.param

As a powerful and easy-to-use testing framework, Pytest provides the @pytest.mark.parametrize decorator to support parameterized testing. The pytest.param function is a tool to further enhance parameterized testing, which allows us to define and control parameterized test cases in a more flexible way.

Parametric identification

In parametric testing, each test case may contain multiple sets of parameters and may produce a large number of test results. At this point, in order to better understand and debug the test results, it makes sense to assign an easy-to-understand identifier to each parameterized test case. The id parameter of the pytest.param function can do this.

For example, in a multiplication test, we can define a parameterized test case as follows:

import pytest
​
@pytest.mark.parametrize("input, expected", [
    pytest.param(2, 4, id="case1"),
    pytest.param(3, 9, id="case2"),
    pytest.param(5, 25, id="case3")
])
def test_multiply(input, expected):
    assert input * input == expected

By using pytest.param for each parameterized test case, we can specify an identifier for each test case, making it easier to read and understand. When the test run is completed, the identification in the test report will help us better locate the problem.

Custom options

In addition to parameter identification, the pytest.param function can also accept additional parameters, such as the marks parameter, which is used to apply custom markers for individual test cases. By using the marks parameter, we can flexibly add various customization options in parameterized tests.

For example, assuming we have a parameterized test case that needs to be skipped, we can define it like this:

import pytest
​
@pytest.mark.parametrize("input, expected", [
    pytest.param(2, 4, marks=pytest.mark.skip),
    pytest.param(3, 9, marks=pytest.mark.skip),
    pytest.param(5, 25)
])
def test_multiply(input, expected):
    assert input * input == expected

In the above example, we use the pytest.mark.skip mark to skip the first two test cases. In this way, we can apply different marks to different parameterized test cases, such as pytest.mark.skip, pytest.mark.xfail, etc., to achieve more flexible test control.

at last

Through the introduction of this article, we have learned about the concept, usage and case demonstration of pytest parameterization. Pytest parameterization can greatly simplify the writing of test cases and improve the reusability and maintainability of the code. In actual software testing, reasonable use of pytest parameterization will help us test more efficiently and find potential problems faster. In addition, we also introduced the advanced usage of parametrize, which can be combined with the hook function pytest_generate_tests to implement complex scenarios. Finally we introduced a very useful function pytest.param, which provides more customization options and control for parameterized tests. Therefore, when writing parameterized tests, you may wish to consider using pytest.param to improve the quality and efficiency of the tests.

Finally: The complete software testing video tutorial below has been compiled and uploaded. Friends who need it can get it by themselves.【保证100%免费】

Insert image description here

Software Testing Interview Document

We must study to find a high-paying job. The following interview questions are the latest interview materials from first-tier Internet companies such as Alibaba, Tencent, Byte, etc., and some Byte bosses have given authoritative answers. After finishing this set I believe everyone can find a satisfactory job based on the interview information.

Insert image description here
Insert image description here

Guess you like

Origin blog.csdn.net/m0_67695717/article/details/133362421