Pytest test framework (4) --- use of decorators

Table of contents

1. Fixture decorator

1.1. Introduction and use of fixture's conftest.py file

1.1.1. Introduction

1.1.2. Example of use

1.2.3, the scope of conftest.py

1.2, multiple fixtures

 1.3. Mutual calls of fixtures

1.4. The scope of the fixture

1.4.1. Introduction

1.4.2, the scope difference between class and function in scope

1.4.3, the difference between the scope of session and module in scope

1.5, fixture use case management

1.5.1, skip use case skip/skipif

1.5.2, marking failed xfail

1.5.3, repeat execution repeat

1.6. Assertion mechanism of fixture

1.6.1, common assertions

1.7. Use the pytest.fiture decorator to carry parameters

1.7.1. Use the parameter params in the test firmware object

1.7.1. Parse the yaml file to transfer data

1.8, mark custom mark

1.8.1, custom mark test cases, and execute test cases according to marks


1. Fixture decorator

1.1 Introduction and use of fixture's conftest.py file

1 . 1 . 1. Introduction

       conftest.py file: It is a very important thing in the pytest framework, which can realize the self-correspondence of fixture objects and automatically apply the cross-template and cross-file application operations, so that the definition of fixture objects is more flexible and convenient;

1 . 1 . 2. Example of use

1. Create a directory demo7, create two directories (test_case1 and test_case2) and a file (congtest.py) in this directory; create test files (test_1.py and test_2.py) in directory test_1, and create test files in test_2 ( test_3.py);

2. Contents of the file conftest.py:

import pytest

@pytest.fixture()
def get_data():
    a = '-------1-------'
    b = 2
    print(a)
    return b

 3. The content of the file test_1.py: (test_1_1 and test_1_3 refer to the general get_data function of the conftest.py file)

import  pytest
def test_1_1(get_data):
    sun = 1+get_data
    print(sun)
    print('-----test_1_1----')

def test_1_2():
    print('-----test_1_2----')


def test_1_3(get_data):
    sun = 2+get_data
    print(sun)
    print('-----test_1_3----')
if __name__ == '__main__':
    pytest.main()

4. Contents of the file test_2.py: (test_2_1 refers to the general get_data function of the conftest.py file)

import  pytest
def test_2_1(get_data):
    sun = 10+get_data
    print(sun)
    print('-----test_2-1----')

def test_2_2():
    print('-----test_2-2----')


def test_2_3():
    print('-----test_2-3----')
if __name__ == '__main__':
    pytest.main()

5. Contents of the file test_3.py: (test_3_1 and test_3_3 refer to the general get_data function of the conftest.py file)

import  pytest
def test_3_1(get_data):
    sun = 100+get_data
    print(sun)
    print('-----test_3_1----')

def test_3_2():
    print('-----test_3_2----')


def test_3_3(get_data):
    sun = 200+get_data
    print(sun)
    print('-----test_3_3----')
if __name__ == '__main__':
    pytest.main(['-s', ['test_3.py']])

 6. Execute three test files (execute all test files in the demo7 directory)

7. Test results:

From the results, it can be seen that the get_data function in conftest.py is referenced across directories and files

1. 2. 3. Scope of conftest.py

1. There can be multiple conftest.py files in a test project. If there are more than one, the scope will be different according to the location of conftest.py; in the root project directory, this file will play a global role. If in In a subdirectory, the file is only valid for the objects under the file and the subdirectories under the current level;

2. The following situations are as follows:

2.1. Add or delete a conftest.py file in the test_case1 directory: (the function name in this file is the same as the function name in conftest.py in the root project directory ( demo 7) )

2.2. File content:

import pytest

@pytest.fixture()
def get_data():
    a = '-------data2-------'
    b = 2
    print(a)
    return b

2.3. Execute all test files under demo7 again, and the test results are as follows: ( The test files in test_case1 refer to the functions in conftest.py in your own directory )

2.4. Summary: When the function name in the root project directory (demo7) conftest.py is the same as the function name in the subdirectory (test_case1) conftest.py, the test file in the test_case1 directory is given priority to use conftest.py in its own directory; and only Objects in the current level and subdirectories do not apply to test_case2; test_case2 uses conftest.py in the demo7 directory (acts globally);

3.1. Add or delete a conftest.py file in the test_case1 directory: (The function name in this file is different from the function name in conftest.py in the root project directory ( demo 7) ) The file test_1.py in test_case1 uses two function;

3.2. Modify test_1.py as follows:

import  pytest
def test_1_1(get_data):   # 引用根工程目录(demo7)下的conftest.py的函数
    sun = 1+get_data
    print(sun)
    print('-----test_1_1----')

def test_1_2():
    print('-----test_1_2----')


def test_1_3(get_data2):  # 引用test_case1下的conftest.py的函数
    sun = 2+get_data2
    print(sun)
    print('-----test_1_3----')
if __name__ == '__main__':
    pytest.main()

 3.3 . Execute the test_1.py file, the result is as follows:

1. 2. Multiple fixtures

1. The sample code of the conftest.py file is as follows:

import pytest

@pytest.fixture()
def get_data1():
    a = '-------100-------'
    b = 20
    print(a)
    return b


@pytest.fixture()
def get_data2():
    a = '-------200-------'
    c = 30
    print(a)
    return c

 2. The test file test_4.py is as follows:

import  pytest
def test_3_1(get_data1, get_data2):
    sun = get_data1+get_data2
    print(sun)
if __name__ == '__main__':
    pytest.main(['-s', 'test_4.py'])

3. The result of executing the test_4.py file is as follows:

 1.3. Mutual calls of fixtures

1. The sample code of the conftest.py file is as follows: (data2 calls data1)

import pytest

@pytest.fixture()
def data1():
    print('----data1---')
    a = 1
    return a


@pytest.fixture()
def data2(data1):    # 调用data1的数据
    print('----data2---')
    b = 2+data1
    return b

2. The test file test_1.py is as follows:

import pytest

def test_1(data2):
    sum = 100+data2
    print(sum)
    assert sum==103

if __name__ == '__main__':
    pytest.main()

3. Execution result:

1. 4. The scope of the fixture

1 . 4 . 1. Introduction

1. There is a parameter scope in the fixture, which can control the scope of the fixture object, and its values ​​include session, module, class, function, package, etc.

1 . 4 . 2. The scope difference between class and function in scope

1. The function in the scope is equivalent to the setup_function at the function level and the setup_method at the class level

2. Let's test the difference between class and function in scope:

2.1, scope='class', the sample code is as follows:

import pytest

@pytest.fixture(scope='class')
# @pytest.fixture(scope='function')
def data1():
    a = 2
    print('----data1----')
    return a

def test_1(data1):
    sum = 1 + data1
    print(sum)
    print('----test1----')

class Test_1():
    def test_2(self, data1):
        sum = 2 + data1
        print(sum)
        print('---test2_1---')

    def test_1(self, data1):
        sum = 3+data1
        print(sum)
        print('---test2_2---')

def test_2(data1):
    sum = 4+data1
    print(sum)
    print('----test2----')

if __name__ == '__main__':
    pytest.main()

 2.2. Execution result: (class type data is only executed once data1, but the data can be passed to all functions in the class; function type use cases will be executed once)

3.1, scope='function', the sample code is as follows:

import pytest

# @pytest.fixture(scope='class')
@pytest.fixture(scope='function')
def data1():
    a = 2
    print('----data1----')
    return a

def test_1(data1):
    sum = 1 + data1
    print(sum)
    print('----test1----')

class Test_1():
    def test_2(self, data1):
        sum = 2 + data1
        print(sum)
        print('---test2_1---')

    def test_1(self, data1):
        sum = 3+data1
        print(sum)
        print('---test2_2---')

def test_2(data1):
    sum = 4+data1
    print(sum)
    print('----test2----')

if __name__ == '__main__':
    pytest.main()

3.2. The execution results are as follows: (acting on functions, all functions in the type test case will be executed once; function type test cases will also be executed once)

4. Summary: The difference between function and class: if the two are applied to the test function, both are equivalent, and both represent the creation of a new firmware object; if it is for the test method in the class, function represents each test The method creates a brand new object, and class means that all test cases in the test class share a test firmware;

1 . 4 . 3. The difference between the scope of session and module in scope

1. The difference between session and module: module means that for a module, the same firmware object is used in each module, and the firmware objects created by different modules are different; while session means the entire session, and the objects used by different modules are also different;

1.5, fixture use case management

1 . 5 . 1. Skip use case skip /skipif

1. @pytest.mark.skip(reason='do not execute')

2. Test file:

import pytest

@pytest.mark.skip(reason='不执行')
def test_01():
    print('---test1---')

def test_02():
    print('---test2---')

if __name__ == '__main__':
    pytest.main()

3. Execution result:

4. @pytest.mark.skipif(2<3,reason='skip use case'); test file:

import pytest

@pytest.mark.skipif(2<3,reason='跳过用例')
def test_01():
    print('---test1---')

def test_02():
    print('---test2---')

if __name__ == '__main__':
    pytest.main()

 5. Execution result:

1 . 5 . 2. Mark failure xfail

1. Code example:

import pytest

def test_01():
    print('---test1---')

@pytest.mark.xfail(reason='跳过')
def test_02():
    print('---test2---')
    assert 1==2

if __name__ == '__main__':
    pytest.main()

2. Execution result:

1 . 5 . 3. Repeat repeat

1. Install the pytest-repeat library first; execute pip install pytest-repeat

2. Sample code: ( specify the number of times the test case is executed by the method of mark mark )

import pytest

def test_01():
    print('---test1---')
@pytest.mark.repeat(5)
def test_02():
    print('---test2---')
    assert 1==1

if __name__ == '__main__':
    pytest.main(['-s','test_0_1.py'])

3. Execution result:

4. Sample code: ( executed by the parameter ' -- count =5 ' , all test cases in the test file will be executed 5 times in sequence )

import pytest

def test_01():
    print('---test1---')

# @pytest.mark.ios
# @pytest.mark.repeat(5)
def test_02():
    print('---test2---')
    assert 1==1

if __name__ == '__main__':
    pytest.main(['-s', 'test_0_1.py', '--count=5'])  # 会对该测试文件里的所有测试用例都执行5次

 5. Execution result: ( after use case 1 is executed 5 times , use case 2 is executed 5 more times )

6. You can use the parameter ' --repeat-scope= session ' to specify the repeated scope ( similar to the scope of the fixture ) , sample code:

import pytest

def test_01():
    print('---test1---')

# @pytest.mark.ios
# @pytest.mark.repeat(5)
def test_02():
    print('---test2---')
    assert 1==1

if __name__ == '__main__':
    pytest.main(['-s', 'test_0_1.py', '--count=5', '--repeat-scope=session'])  # 整个文件的用例执行五次

 7. Execution result: ( the use case of the entire file is executed five times )

8. Combining --count =10000 and -X parameters, in the specified number of repeated executions, if the first use case execution fails, it will stop and continue to execute ;

9. --repeat-scope Summary:

9.1, function: default parameter, the scope of action is to execute the next test case after repeated execution of each test case;

9.2, class: Indicates that the use cases in the class are repeatedly executed in units of use case sets;

9.3, module: Indicates that the use cases in the module are repeatedly executed in units of modules;

9.4, session: Indicates that the entire test session is used as a unit, that is, all collected tests are executed once, and then all use cases are executed once;

1.6 . Assertion mechanism of fixture

1 . 6 . 1. Common assertions

1. Common assertions:

1.1. assert XX: judge that XX is true;

1.2, assert not XX: judge that XX is not true;

1.3. assert a in b: judge that b contains a;

1.4, assert a==b: judge that a is equal to b;

1.5. assert a !=b: judge that a is not equal to b;

2. Sample code:

import pytest

class Test_a():

    def test_1(self):
        assert 1

    def test_2(self):
        assert 'sh' in 'shell'

    def test_3(self):
        assert 1==1

    def test_4(self):
        assert 'shell' != 'shell_1'

if __name__ == '__main__':
    pytest.main(['-s','test_1.py'])

3. Execution result:

1.7 . Use pytest.ficture decorator to carry parameters

1. 7. 1. Use the parameter params in the test firmware object

1. Sample code:

import pytest

class Calculater():
    def add(self,a,b):
        return a+b

@pytest.fixture(scope='function', params=[[1,2,3],[2,3,5]])
def get_data(request):
    # print(Calculater())
    # print(request.param)
    return Calculater(), request.param

def test_add(get_data):
    print(get_data[1][0], get_data[1][1], get_data[1][2])
    assert get_data[0].add(get_data[1][0], get_data[1][1]) == get_data[1][2]

if __name__ == '__main__':
    pytest.main(['-s', 'test_2.py'])

 2. Test results:

3. Summary: If the params method is used to achieve parameterization , the parameter name passed in the firmware object is always request , and the parameter object must be returned , that is, request.params ;

4. The parameterized function can also be directly placed in conftest.py, which can separate the firmware initialization code and the parameterized code to realize two functions;

5. conftest.py file, sample code:

import pytest

class Calculater():
    def add(self, a, b):
        return a+b

@pytest.fixture(scope='function')
def get_data():
    return Calculater()

@pytest.fixture(params=[[1,2,3],[2,2,4]])
def get_data_params(request):
    return request.param

6. Test code:

import pytest


def test_add_1(get_data, get_data_params):
    print(get_data_params[0],get_data_params[1],get_data_params[2])
    assert get_data.add(get_data_params[0],get_data_params[1])==get_data_params[2]

if __name__ == '__main__':
    pytest.main(['-s', 'test_3.py'])

 7. Test results:

 8. It can also be implemented in the following form, sample code:

import pytest

class Calculater_2(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def add(self):
        return self.a+self.b

@pytest.fixture(scope='function', params=[[1,1,2],[3,3,6]])
def get_data_params_2(request):
    return Calculater_2(request.param[0], request.param[1]), request.param[2]

def test_sum(get_data_params_2):
    return get_data_params_2[0].add() == get_data_params_2[1]

if __name__ == '__main__':
    pytest.main(['-s', 'test_4.py', '--disable-warnings'])

9. Execution result:

1 . 7 . 1. Parse the yaml file to transfer data

1. The yaml file is as follows: data.yaml

data:
  - [1,1,2]
  - [2,2,4]
  - [3,3,6]

2. Parse the yaml file: read_yaml.py

import yaml


def readyaml():
    with open('data.yaml', encoding='utf-8') as f:
        get_yaml = yaml.load(f, Loader=yaml.FullLoader)
    print(get_yaml)
    print(type(get_yaml))
    return get_yaml['data']

# readyaml()

3. conftest.py file (change params=specific value to the value parsed from yaml)

import pytest
from demo10.read_yaml import *   # 导入解析yaml得到yaml值的文件
class Calculater():
    def add(self, a, b):
        return a+b

@pytest.fixture(scope='function')
def get_data():
    return Calculater()

@pytest.fixture(params=readyaml())
def get_data_params(request):
    return request.param

 4. Test file: test_4.py

import pytest



def test_add_1(get_data, get_data_params):
    print(get_data_params[0],get_data_params[1],get_data_params[2])
    assert get_data.add(get_data_params[0],get_data_params[1])==get_data_params[2]

if __name__ == '__main__':
    pytest.main(['-s', 'test_3.py'])

5. Execute the test_4.py file to get the result:

1. 8. mark custom mark

1 . 8 . 1. Customize mark test cases and execute test cases according to marks

1. Code example: (5 test cases are marked differently, use case 1 is marked as: L1; use cases 2 and 3 are marked as: apple; use cases 4 and 5 are marked as: case1)

import pytest

@pytest.mark.L1
def test_1():
    print('---test1---')

@pytest.mark.apple
def test_2():
    print('---test2---')

@pytest.mark.apple
def test_3():
    print('---test3---')

@pytest.mark.case1
def test_4():
    print('---test4---')
@pytest.mark.case1
def test_5():
    print('---test5---')

if __name__ == '__main__':
    pytest.main(['-s', 'test_5.py', '-m case1', '--disable-warnings'])

 2. Execution result: (The result below is the execution marked as: case1)

3. If you want to execute all use cases marked as case1; parameters in the file:

pytest.main(['-s''test_5.py''-m not case1''--disable-warnings'])

When executed in the terminal , the command line is as follows : pytest -s test_5.py -m "not case1" 

Results of the:

​​​​​​​

3. Summary directly add the parameter ' -m case1 ' or ' -m =case1 ' ; execute all use cases except the selected mark , when adding parameters in the test file  ' -m not case1', when executing the command line in the terminal : -m "not case1"

Guess you like

Origin blog.csdn.net/weixin_44701654/article/details/128176992