Python testing tool-detailed explanation of using Pytest

1. Introduction

Pytest is a full-featured Python testing tool that supports third-party extension plug-ins and can be used to carry out unit testing and complex functional testing. It can be used in conjunction with selenium, requests, appium and other modules to implement automated testing of WEB UI, API, and APP.
For details, see the reference guide document: https://docs.pytest.org/en/7.1.x/#
PDF document: https://media.readthedocs.org/pdf/pytest/latest/pytest.pdf

2. Installation

0. Prerequisite: Python 3.7+ environment has been installed and configured.
1. Installation: Run the command pip install pytest in the command line or Pycharm terminal
2. Verification: Run the command pytest --version, and the installed pytest version number will be displayed.

3. Use

1. Pytest rules for discovering test cases

  • py file starts with test_ or ends with _test;
  • The class name starts with Test;
  • Test cases start with test_.

2. Basic operation of Pytest

Write a use case according to the Pytest discovery use case rules and run it directly. You can call Pytest to collect test cases that comply with the rules, execute the use cases, and print execution status and other information.

"""
   Pytest运行demo
"""

class TestPytest():
    """测试Pytest类"""

    def test_001(self):
        """test_001"""
        print("test_001")
        assert 1 == 1

    def test_002(self):
        """test_002"""
        print("test_002")
        assert 1 < 2

    def test_003(self):
        """test_003"""
        print("test_003")
        assert 1 != 2

    def test_004(self):
        """test_004"""
        print("test_004")
        assert 1 == 0

The red box in the figure below gives the test result information, including the execution results and time consumption of each use case, the number of passes and failures, etc.; the blue part is the startup information; the green box part presents the execution process, assertion failure and other information .

3. Commonly used functions

3.1 Configuration file pytest.ini

The pytest.ini file takes precedence over other files. Even if it is empty, various execution parameters can be configured in this file. See the examples below for details.

3.2 Execution parameters

  • -v displays detailed test information: pytest.main(['-v']), which displays each use case method name, results, progress and other information in detail

Insert image description here

  • -s displays print information in the use case: pytest.main(['-s'])

  • -q Quiet output, only display the results of the operation:pytest.main(['-q', '../Testcase/test_pytest_demo.py'])

  • -n Multi-thread execution test case: pytest.main(['-vs', '../Testcase/test_pytest_demo.py','-n2']), the number after n is the number of threads, you need to install the pytest-xdist plug-in before use

  • -m executes the use case of the specified mark: pytest.main(['-m markName']). The customized mark name can be used after being registered in the pytest.ini file.

  • -lf reruns the last failed test case: pytest.main(['-v', '.../Testcase/test_pytest_demo.py', '-lf']), if there was no failed test case last time, execute all


  • –reruns=N Automatically rerun failed test cases N times:pytest.main(['-q', '--reruns=1', '../Testcase/test_pytest_demo.py'])

Insert image description here

3.3 Scope of execution

  • Execute all use cases: pytest.main()
  • Execute the use case in the specified directory: pytest.main(['…/Testcase'])
  • Execute the use case of the specified module: pytest.main(['…/Testcase/test_pytest_demo.py'])
  • Execute the use case of the specified class: pytest.main(['.../Testcase/test_pytest_demo.py::TestPytest'])
  • Use case to execute the specified method: pytest.main(['.../Testcase/test_pytest_demo.py::TestPytest::test_001'])
    In addition, you can also configure the search rules when executing the use case through pytest.ini, example:
[pytest]
testpaths = ../TestCase
python_files =  test_*.py
python_classes = Test*
python_functions = test_*

3.4 Fixture device

Decorators @pytest.fixturecan be used to set pre- and post-conditions for use case execution. In the decorated method, the pre- and post-condition functions can be realized through the use of yield. Yield has the return function of return, but the code after yield can be executed (if function A has yield, it will be considered as a generated function when executed. When function A reaches yield, it will jump out of function A and execute code segment B that calls function A. After code segment B is executed, it will continue to return to function A to execute the code after yield).

Fixture setting method:

@pytest.fixture(scope=scope, params=Iterable[object], autouse=True, ids=Iterable[object], name=fixtureName)
  • scope: scope

    • function: Default scope, use case level, destroy the folder device at the end of the test case (that is, the code after executing yield);
    • class: Acts on a class. The device is destroyed when the last test in the class ends;
    • module: Acts on the module (.py file), the last test destruction device in the module;
    • package: Acts on the python package, the last test destruction device in the package;
    • Session: Acts on the session. The last test in the session destroys the device.
  • params: Parameters, the format is a tuple or list. The number of parameters will determine the number of executions. The decorated method needs to inherit request (non-interface requests), use request.param to obtain parameters, one at a time;

  • autouse: Automatically call settings, True/False, default is False;

  • ids: test ID, format is tuple or list;

  • name: The device name can be set through name. After setting, the name needs to be passed in when calling.

Fixture calling method:

  • Method 1: Directly pass it as a parameter to the test case call
  • Method 2: Call through decorator@pytest.mark.usefixtures('fixtureName1', 'fixtureName2')
  • Method 3: When setting up the device, set Automatic Call to True@pytest.fixture(autouse=True)

Sample code for fixture setting and calling:

"""
   Pytest fixture装置使用示例
"""
import pytest

@pytest.fixture
def human():
    """未设置参数,默认为用例级,谁调用谁享用"""
    name = '小明'
    print('前置条件:小明醒了,未设置参数,谁调用谁享用')
    yield name
    print('后置条件:小明睡了,未设置参数,谁调用谁享用')

@pytest.fixture(autouse=True)
def weather():
    """作用范围:默认为用例级,设置自动调用为True,每个用例都会调用"""
    sun = '晴天'
    print('前置条件:天亮了autouse=True,默认作用每个用例')
    yield sun
    print('后置条件:天黑了autouse=True,默认作用每个用例')

@pytest.fixture(scope='class')
def time():
    """作用范围:类级,谁调用谁享用"""
    t1 = '6:00'
    t2 = '20:00'
    print('前置条件:早上好class')
    yield t1, t2
    print('后置条件:晚上好class')

@pytest.fixture(scope='session', autouse=True)
def session():
    """作用范围:会话级,yield后可不写内容,不返回数据,设置自动调用为True"""
    print('前置条件:会话开始-----------------')
    yield
    print('后置条件:会话结束-----------------')

@pytest.fixture(params=['小美', '小帅'], ids=('mei', 'shuai'), name='clm')
def params_classmate(request):
    """作用范围:默认用例级,谁调用谁享用params和ids"""
    param = request.param
    print(f'前置条件:{param}')
    yield param
    print(f'后置条件:{param}')

class TestOneDay1:
    """调用方式一,直接作为参数传给测试用例:human,time,weather,clm"""
    def test_eat(self, human, time, clm):
        t1, t2 = time
        print(human + t1 + f'和{clm}吃了早餐')
    def test_school(self, weather, human):
        print('今天是'+weather+','+human + '去上学了')

@pytest.mark.usefixtures('human','time')
class TestOneDay2:
    """调用方式二:通过装饰器调用'human'和'time',但无法传递参数"""
    def test_homework(self):
        print('交了作业')
    def test_home(self):
        print('放学独自回家')

class TestOneDay3:
    """没有直接调用fixture,但设置了自动调用的会被调用"""
    def test_water(self):
        print('喝了水')
    def test_wc(self):
        print('去了wc')

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

operation result:

============================= test session starts =============================
collecting ... collected 7 items

test_pytest_fixture_demo.py::TestOneDay1::test_eat[mei] 前置条件:会话开始-----------------
前置条件:早上好class
前置条件:天亮了autouse=True,默认作用每个用例
前置条件:小明醒了,未设置参数,谁调用谁享用
前置条件:小美
PASSED           [ 14%]小明6:00和小美吃了早餐
后置条件:小美
后置条件:小明睡了,未设置参数,谁调用谁享用
后置条件:天黑了autouse=True,默认作用每个用例

test_pytest_fixture_demo.py::TestOneDay1::test_eat[shuai] 前置条件:天亮了autouse=True,默认作用每个用例
前置条件:小明醒了,未设置参数,谁调用谁享用
前置条件:小帅
PASSED         [ 28%]小明6:00和小帅吃了早餐
后置条件:小帅
后置条件:小明睡了,未设置参数,谁调用谁享用
后置条件:天黑了autouse=True,默认作用每个用例

test_pytest_fixture_demo.py::TestOneDay1::test_school 前置条件:天亮了autouse=True,默认作用每个用例
前置条件:小明醒了,未设置参数,谁调用谁享用
PASSED             [ 42%]今天是晴天,小明去上学了
后置条件:小明睡了,未设置参数,谁调用谁享用
后置条件:天黑了autouse=True,默认作用每个用例
后置条件:晚上好class

test_pytest_fixture_demo.py::TestOneDay2::test_homework 前置条件:早上好class
前置条件:天亮了autouse=True,默认作用每个用例
前置条件:小明醒了,未设置参数,谁调用谁享用
PASSED           [ 57%]交了作业
后置条件:小明睡了,未设置参数,谁调用谁享用
后置条件:天黑了autouse=True,默认作用每个用例

test_pytest_fixture_demo.py::TestOneDay2::test_home 前置条件:天亮了autouse=True,默认作用每个用例
前置条件:小明醒了,未设置参数,谁调用谁享用
PASSED               [ 71%]放学独自回家
后置条件:小明睡了,未设置参数,谁调用谁享用
后置条件:天黑了autouse=True,默认作用每个用例
后置条件:晚上好class

test_pytest_fixture_demo.py::TestOneDay3::test_water 前置条件:天亮了autouse=True,默认作用每个用例
PASSED              [ 85%]喝了水
后置条件:天黑了autouse=True,默认作用每个用例

test_pytest_fixture_demo.py::TestOneDay3::test_wc 前置条件:天亮了autouse=True,默认作用每个用例
PASSED                 [100%]去了wc
后置条件:天黑了autouse=True,默认作用每个用例
后置条件:会话结束-----------------


============================== 7 passed in 0.02s ==============================

进程已结束,退出代码0

In the above example, the methods and test cases under the fixture device are written in the same file, but in actual applications, the contents need to be written in a separate py file. In this case, you can use the help of the conftest.py file , better manage these fixture installation methods (pre- and post-condition use cases).

The conftest.py file is a way to provide fixtures for the entire directory. The fixture device can be written to the file. There is no need to import it when using it. When running the test case, pytest will automatically discover the file. But it should be noted that the scope of conftest.py is all test cases in the directory and subdirectories where it is located (for example, all test cases are in the TestCase directory, then the conftest.py file needs to be placed in the TestCase directory)

conftest.py sample code:

"""
测试conftest.py文件
"""
import pytest

@pytest.fixture(autouse=True)
def test_conftest():
    print("conftest.py中的前置条件")
    yield
    print("conftest.py中的后置条件")

Test case code:

"""
   conftest.py文件使用
"""
import pytest

class TestPytest():
    """测试Pytest类"""

    def test_001(self):
        """test_001"""
        print("test_001")
        a = "a"
        assert a == a

    def test_002(self):
        """test_002"""
        print("test_002")
        assert 1 < 2

operation result:

..\Testcase\test_pytest_demo.py::TestPytest::test_001 conftest.py中的前置条件
test_001
PASSEDconftest.py中的后置条件
..\Testcase\test_pytest_demo.py::TestPytest::test_002 conftest.py中的前置条件
test_002
PASSEDconftest.py中的后置条件
============================== 2 passed in 0.01s ==============================
进程已结束,退出代码0

3.5 Parameterization

Through the decorator @pytest.mark.parametrize('paramNameN', 'paramValue'), the test data can be parameterized to achieve data-driven purposes. The parameter name is passed directly in the form of a string, and the parameter value can be a tuple, Lists, functions (custom functions need to be passed in in list form), etc. Data-driven testing can also be implemented by combining it with csv and yaml files.
Parameterized example code:

"""
   Pytest @pytest.mark.parametrize参数化装饰器使用示例
"""
import random
import pytest


def random_num():
    return random.randint(88888888, 99999999)

class TestParametrize():
    """@pytest.mark.parametrize参数化装饰器"""

    @pytest.mark.parametrize('name', ('小明', '小帅', '小黑'))
    def test_001(self, name):
        """1个参数"""
        print(f"{name}")
        assert 1 == 1

    @pytest.mark.parametrize('name, age', [('小明', '18'), ('小帅', '17'), ('小黑', '16')])
    def test_002(self, name, age):
        """多个参数"""
        print(f"{name}:{age}")
        assert 1 == 1

    @pytest.mark.parametrize('caseinfo', [{'name': '小明', 'age': '18', 'except': 'true'},
                                          {'name': '小美', 'age': '17', 'except': 'true'}])
    def test_003(self, caseinfo):
        """以列表字典形式传入多组测试数据"""
        name = caseinfo['name']
        age = caseinfo['age']
        print(f"{name}:{age}")
        assert caseinfo['except'] == 'true'

    @pytest.mark.parametrize('num', [random_num()])
    def test_004(self, num):
        """传入随机数"""
        print(f"中奖号码为:{num}")
        assert 1 == 1

    @pytest.mark.parametrize('num', range(5))
    def test_005(self, num):
        """传入一个range(函数)"""
        print(f"序号:{num}")
        assert 1 == 1

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

operation result:

============================= test session starts =============================
collecting ... collected 14 items
test_pytest_mark_parametrize.py::TestParametrize::test_001[\u5c0f\u660e] PASSED [  7%]小明
test_pytest_mark_parametrize.py::TestParametrize::test_001[\u5c0f\u5e05] PASSED [ 14%]小帅
test_pytest_mark_parametrize.py::TestParametrize::test_001[\u5c0f\u9ed1] PASSED [ 21%]小黑
test_pytest_mark_parametrize.py::TestParametrize::test_002[\u5c0f\u660e-18] PASSED [ 28%]小明:18
test_pytest_mark_parametrize.py::TestParametrize::test_002[\u5c0f\u5e05-17] PASSED [ 35%]小帅:17
test_pytest_mark_parametrize.py::TestParametrize::test_002[\u5c0f\u9ed1-16] PASSED [ 42%]小黑:16
test_pytest_mark_parametrize.py::TestParametrize::test_003[caseinfo0] PASSED [ 50%]小明:18
test_pytest_mark_parametrize.py::TestParametrize::test_003[caseinfo1] PASSED [ 57%]小美:17
test_pytest_mark_parametrize.py::TestParametrize::test_004[92025408] PASSED [ 64%]中奖号码为:92025408
test_pytest_mark_parametrize.py::TestParametrize::test_005[0] PASSED     [ 71%]序号:0
test_pytest_mark_parametrize.py::TestParametrize::test_005[1] PASSED     [ 78%]序号:1
test_pytest_mark_parametrize.py::TestParametrize::test_005[2] PASSED     [ 85%]序号:2
test_pytest_mark_parametrize.py::TestParametrize::test_005[3] PASSED     [ 92%]序号:3
test_pytest_mark_parametrize.py::TestParametrize::test_005[4] PASSED     [100%]序号:4

============================= 14 passed in 0.02s ==============================

进程已结束,退出代码0

3.6 Log

By configuring log-related parameters in pytest.ini, the log function can be realized. Configurable parameters:

  • log_level: capture/display log level, CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET, default WARNING;
  • log_format: log format;
  • log_date_format: log time format;
  • log_cli_level: real-time log level;
  • log_cli_format: real-time log format;
  • log_cli_date_format: real-time log time format;
  • log_file: log writing file path (overwrite writing);
  • log_file_level: The log level written to the log file;
  • log_file_format: log format written to the log file;
  • log_file_date_format: the time format written to the log file;
  • log_auto_indent: Automatically indent multi-line messages passed to the log module, accepts true/on, false/off or integer.

pytest.ini configuration log example:

[pytest]
addopts = -vs --color=yes
log_cli = true

log_level = DEBUG
log_format = %(asctime)s %(filename)-s:%(lineno)-s %(levelname)-8s - %(message)s
log_date_format = %Y/%m/%d %H:%M:%S
log_auto_indent = true

log_file = ../Log/logs.log
log_file_level = DEBUG
log_file_format = %(asctime)s %(filename)-s:%(lineno)-s %(levelname)-8s - %(message)s
log_file_date_format = %Y/%m/%d %H:%M:%S

Log sample code:

"""
   Pytest logging使用示例
"""
from time import sleep
import logging
logger = logging.getLogger('__name__')

def test_pytest_log():
    logger.info('info msg')
    logger.debug('debug msg')
    logger.error('error msg')
    sleep(3)
    logger.critical('critical msg')
    sleep(3)
    logger.warning('warning msg')
    logger.info('info \n多行缩进\n多行缩进\n多行缩进')

if __name__ == '__main__':
    test_pytest_log()

Running results - console:


Running results - log file:

Insert image description here

3.7 Affirmations

You can directly use the assertions that come with python. The specific presentation form can be seen in the previous sample code.

  • assert a in b: assert that a contains b
  • assert a == b: assert that a is equal to b
  • assert 1 < 2: assert that 1 is less than 2
  • ……

3.8 Generate test report

Method 1: Install the third-party plug-in pytest-allure. For detailed usage, see the basic application of Allure in the Pytest automated testing framework.


Method 2: Install the third-party plug-in pytest-html, and add --html=../report/report.htmlparameters to produce an html report
and run it .pytest.main(['-vs', '--html=../report/report.html', '../Testcase/test_pytest_report_html.py'])

Insert image description here
Note: This article is a study note, including errors, optimizations, etc. Welcome to communicate and correct.


Finally, I will share with you the documents and learning materials that I have accumulated and truthful. If necessary, you can just pick them up. The above content should

be the most comprehensive and complete preparation warehouse for software testing friends. In order to better organize it For each module, I also referred to many high-quality blog posts and projects on the Internet, trying not to miss any knowledge point. Many friends relied on these contents to review and got offers from major manufacturers such as BATJ. This warehouse has also helped a lot. As a learner of software testing, I hope it can also help you.

Follow my WeChat official account below to get it for free! ↓ ↓ ↓ ↓ ↓

Guess you like

Origin blog.csdn.net/weixin_56331124/article/details/132766568
Recommended