一、挑选用例执行

Pytest 可以灵活的挑选测试用例执,方式如下:

1.1.指定一个模块

可以像这样只挑选一个模块执行

pytest .\autotest\test_case01.py

1.2.指定目录

可以像这样只挑选一个目录执行

pytest test_case

也可以指定多个目录

pytest autotest  test_case

1.3.指定模块里面的函数或者类

指定一个类

pytest .\test_case\test_case01.py::Test_pass

也可以指定类里面的方法

pytest .\test_case\test_case01.py::Test_pass::test_02

1.4.根据名字

可以使用 命令行参数 -k 后面加名字来挑选要执行的测试项,比如像这样后面跟测试函数名字的一部分:

pytest -k test_33 -s

注意,-k 后面的名字

  • 可以是测试函数的名字,可以是类的名字,可以是模块文件名,可以是目录的名字
  • 是大小不敏感
  • 不一定要完整,只要能有部分匹配上就行
  • 可以用 not 表示选择名字中不包含,比如
pytest -k "not test_33" -s
  • 可以用 and 表示选择名字同时包含多个关键字,比如
pytest -k "33 and test" -s
  • 可以用 or 表示选择名字 包含指定关键字之一即可,比如
pytest -k "33 or test" -s

1.5.根据标签

参考官方文档

  • 可以这样给 某个方法加上标签 weblogintest
import pytest

class Test_pass:
    @pytest.mark.weblogintest
    def test_02(self):
        print('********用例02********')
        assert 2 == 2

然后,可以这样运行指定标签的用例

pytest test_case -m weblogintest -s
  • 也可以这样给整个类加上标签
import pytest

@pytest.mark.weblogintest
class Test_pass:

    def test_01(self):
        print('********用例01********')
        assert 1 == 1

    def test_02(self):
        print('********用例02********')
        assert 2 == 2

    def test_03(self):
        print('********用例03********')
        assert 3 == 2
  • 可以这样同时添加多个标签
import pytest

@pytest.mark.weblogintest
@pytest.mark.webapilogintest
class Test_pass:

    def test_01(self):
        print('********用例01********')
        assert 1 == 1

    def test_02(self):
        print('********用例02********')
        assert 2 == 2

    def test_03(self):
        print('********用例03********')
        assert 3 == 2
  • 可以定义一个全局变量 pytestmark 为 整个模块文件 设定标签

使用pytestmark全局变量:在模块文件的顶部,在导入语句之前,定义一个pytestmark变量,该变量是一个装饰器。使用该装饰器可以给整个模块文件设置标签。示例如下:

import pytest

pytestmark = pytest.mark.slow

def test_func1():
    print("************test_func1************")
    assert True

def test_func2():
    print("************test_func2************")
    assert True

根据上面的标签slow执行如下:

Pytest测试用例执行_重新运行

  • 如果需要定义多个标签,可以定义一个列表
import pytest

# 定义多个标签,放在列表中
pytestmark = [pytest.mark.login, pytest.mark.weblogintest, pytest.mark.webapilogintest]

def test_func1():
    print("************test_func1************")
    assert True

def test_func2():
    print("************test_func2************")
    assert True

执行后如下:

Pytest测试用例执行_测试用例_02

二、skip、skipif跳过用例

2.1.场景说明

  • pytest.mark.skip 可以标记无法在某些平台上运行的测试功能,或者您希望失败的测试功能
  • 希望满足某些条件才执行某些测试用例,否则pytest会跳过运行该测试用例
  • 实际常见场景:跳过非Windows平台上的仅Windows测试,或者跳过依赖于当前不可用的外部资源(例如数据库)的测试

2.2.@pytest.mark.skip注解

@pytest.mark.skip作用是跳过执行测试用例,有可选参数reason:跳过的原因,会在执行结果中打印

import pytest

class Test_pass:
    def test_01(self):
        print('********用例01********')
        assert 1 == 1

    def test_02(self):
        print('********用例02********')
        assert 2 == 2

    @pytest.mark.skip(reason="不在本次测试范围,所以跳过cls")
    def test_03(self):
        print('********用例03********')
        assert 3 == 2

执行发现test_03执行跳过没有执行,如下:

Pytest测试用例执行_用例_03

注意:

  • @pytest.mark.skip 可以加在函数上,类上,类方法上
  • 如果加在类上面,类里面的所有测试用例都不会执行

pytest.skip(msg="",allow_module_level=False),设置参数当 allow_module_level=True 时,可以设置在模块级别跳过整个模块,test_case13.py如下:

import pytest

@pytest.skip(msg="整个模块跳过", allow_module_level = True)
class Test_pass:
    def test_01(self):
        print('********用例01********')
        assert 1 == 1

    def test_02(self):
        print('********用例02********')
        assert 2 == 2

    @pytest.mark.skip(reason="不在本次测试范围,所以跳过cls")
    def test_03(self):
        print('********用例03********')
        assert 3 == 2

执行结果如下,发现test_case13.整个模块跳过没有执行:

Pytest测试用例执行_用例_04

2.3.@pytest.mark.skipif(condition, reason="")

@pytest.mark.skipif(condition, reason=""),作用主要是希望有条件地跳过某些测试用例

注意:condition需要返回True才会跳过

案例代码如下:

import sys
import pytest

@pytest.mark.skipif(sys.platform=="win32", reason="在Windows系统上不能运行")
class Test_pass:
    def test_01(self):
        print('********用例01********')
        assert 1 == 1

    def test_02(self):
        print('********用例02********')
        assert 2 == 2

    def test_03(self):
        print('********用例03********')
        assert 3 == 2

执行发现由于系统是win32,条件满足所以不执行

Pytest测试用例执行_重新运行_05

2.4.跳过标记

可以将 pytest.mark.skip 和 pytest.mark.skipif 赋值给一个标记变量,在不同模块之间共享这个标记变量,若有多个模块的测试用例需要用到相同的 skip 或 skipif ,可以用一个单独的文件去管理这些通用标记,然后适用于整个测试用例集

import sys
import pytest

# 赋值给一个标记变量
skipmark = pytest.mark.skip(reason="不在测试范围之内,跳过不执行")
skipifmark = pytest.mark.skipif(sys.platform == "win32", reason="在Windows系统上不能运行")

class Test_pass:
    def test_01(self):
        print('********用例01********')
        assert 1 == 1

    @skipifmark
    def test_02(self):
        print('********用例02********')
        assert 2 == 2

    @skipmark
    def test_03(self):
        print('********用例03********')
        assert 3 == 2

执行发现test_03和test_02由于添加了标记变量作为装饰器,跳过了用例不执行,如下:

Pytest测试用例执行_用例_06

三、失败重跑插件pytest-rerunfailures

pytest-rerunfailures是一个用于在测试失败时重新运行失败的测试用例的Pytest插件。它可以在测试失败后自动重新运行指定次数的失败用例,以提高测试用例的可靠性和稳定性。

3.1.安装插件

pip3 install pytest-rerunfailures -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com

执行有两种方式,命令行和装饰器方式如下:

  • 命令行参数:--reruns n(重新运行次数),--reruns-delay m(等待运行秒数)
  • 装饰器参数:reruns=n(重新运行次数),reruns_delay=m(等待运行秒数)

3.2.重新运行所有失败的用例

要重新运行所有测试失败的用例,使用 --reruns 命令行选项,并指定要运行测试的最大次数:

pytest --reruns 5 -s

运行失败的 fixture 或 setup_class 也将重新执行,案例:准备测试用例test_case01.py:

import pytest

class Test_pass:

    def test_01(self):
        print('********用例01********')
        assert 1 == 1

    def test_02(self):
        print('********用例02********')
        assert 2 == 2

    def test_03(self):
        print('********用例03********')
     # 故意制造错误
        1 / 0
        assert 3 == 2

执行代码如下,会发现test_03被执行了6次,其中5次是设置的重试的次数:

Pytest测试用例执行_用例_07

3.3.添加重新运行的延时

要在两次重试之间增加延迟时间,使用 --reruns-delay 命令行选项,指定下次测试重新开始之前等待的秒数

pytest --reruns 5 --reruns-delay 10 -s

上面设置后会重试5次,每次重试间隔10s后继续下一次重试

3.4.重新运行指定的测试用例

要将单个测试用例添加flaky装饰器 @pytest.mark.flaky(reruns=5) ,并在测试失败时自动重新运行,需要指定最大重新运行的次数,下面用例test_03添加了装饰器,如下:

import pytest

# @pytest.mark.weblogintest
# @pytest.mark.webapilogintest
class Test_pass:

    def test_01(self):
        print('********用例01********')
        assert 1 == 1

    def test_02(self):
        print('********用例02********')
        assert 2 == 2

   # 通过装饰器设置失败重试
    @pytest.mark.flaky(reruns=5, reruns_delay=10)
    def test_03(self):
        print('********用例03********')
        1 / 0
        assert 3 == 2

执行后如下:

Pytest测试用例执行_用例_08

注意:

  • 如果指定了用例通过装饰器指定了重新运行次数,则在命令行运行的时候添加 --reruns 对这些用例是不会生效的 

3.5.失败重跑插件的兼容性

虽然pytest-rerunfailures插件可以提供测试失败重跑的功能,但在使用时需要注意其与@pytest.fixture()装饰器、pytest-xdist--looponfail标志和核心的--pdb标志的不兼容性。根据具体的需求和情况,选择合适的插件和标志来进行测试。

  • pytest-rerunfailures插件不支持与@pytest.fixture()装饰器一起使用。这是因为pytest-rerunfailures插件在测试失败时会重新运行整个测试用例,包括其中的fixture。如果fixture具有副作用或者状态,可能会导致不可预测的行为。因此,建议不要在使用pytest-rerunfailures插件时与@pytest.fixture()装饰器一起使用。
  • pytest-rerunfailures插件与pytest-xdist的--looponfail标志不兼容。--looponfail标志是pytest-xdist插件提供的功能,它可以在测试失败时自动重新运行失败的测试用例。由于这两个插件都提供了类似的功能,因此在使用时可能会产生冲突。建议在使用pytest-rerunfailures插件时不要同时使用--looponfail标志。
  • pytest-rerunfailures插件与核心的--pdb标志不兼容。--pdb标志用于在测试失败时自动进入Python调试器(PDB)以进行调试。由于pytest-rerunfailures插件会在测试失败时重新运行失败的测试用例,这可能会导致PDB会被多次触发,从而导致不可预测的行为。因此,在使用pytest-rerunfailures插件时不建议同时使用--pdb标志。

四、配置文件pytest.ini

4.1.pytest.ini文件说明

pytest.ini是一个配置文件,用于自定义和配置Pytest运行测试时的行为和选项。通过在项目根目录下创建名为pytest.ini的文件并在其中定义配置选项,可以修改和扩展Pytest的默认行为。

pytest里面有些文件是非test文件

  • pytest.ini:pytest的主配置文件,可以改变pytest的默认行为
  • conftest.py:测试用例的一些fixture配置
  • _init_.py:识别该文件夹为python的package包

查看pytest.ini的配置选项,cmd执行

pytest --help

4.2.pytest.ini应该放哪里?

pytest.ini放在项目根目录下 ,位置不要随便改动,名字是固定的不要修改。

4.3.常见配置

4.3.1.marks

  • 作用:如果测试用例中添加了 @pytest.mark.webinfotest 装饰器,如果不添加marks选项的话,就会报warnings
  • 格式:list列表类型

案例如下:

[pytest]
markers =
    slow: mark a test as slow
    smoke: mark a test as smoke
    api: mark a test as an API test

上面markers:定义了测试用例标签(markers)。在这个示例中,定义了slowsmokeapi三个标签,用于对测试用例进行分类和选择性运行。

4.3.2.xfail_strict

  • 作用:设置xfail_strict = True可以让那些标记为@pytest.mark.xfail,但实际通过显示XPASS的测试用例被报告为失败
  • 格式:True 、False(默认),1、0

在Pytest中,.xfail_strict是一个标记,用于指定关于"expected failure"的严格级别。它可以用于在某些测试用例上标记为"expected failure",并根据指定的级别来处理这些失败。.xfail_strict的作用是控制关于被标记为"expected failure"的用例的处理方式。它有以下几个选项:

  • True:严格模式,如果标记为"expected failure"的用例在执行时没有失败(即断言成功),则会被视为失败。这意味着,即使预期这些用例会失败,但如果它们成功了,测试运行结果也会显示为失败。
  • False:宽松模式,如果标记为"expected failure"的用例在执行时没有失败(即断言成功),则会被视为通过。这意味着,即使这些用例成功了,测试运行结果也会显示为通过。
  • 'no':与False相同,这是.xfail标记的默认行为,表示宽松模式。

案例

[pytest]
markers =
    slow: mark a test as slow
    smoke: mark a test as smoke
    api: mark a test as an API test
xfail_strict = True

准备测试用例如下:

import pytest

@pytest.mark.xfail()
def test_20():
    assert "a" != "b"

未设置 xfail_strict = True 时,执行用例,测试结果显示XPASS

Pytest测试用例执行_用例_09

已设置 xfail_strict = True 时,测试结果显示failed

Pytest测试用例执行_用例_10

4.3.3.addopts

addopts参数可以更改默认命令行选项,这个当我们在cmd输入一堆指令去执行用例的时候,就可以用该参数代替了,省去重复性的敲命令工作,例如:当测试结束生成报告,失败重跑两次,一共运行两次,通过分布式去测试,如果在cmd中写的话,命令会很长

pytest -v --rerun=2 --count=2 --html=report.html --self-contained-html -n=auto

通过pytest.ini 中addopts就可以解决这个问题,命令如下:

addopts = -vs -rsxX -l --tb=short --reruns=1 -count=2 --html=./result/reports.html --self-contained-html

参数:

  • -v:表示输出详细的测试结果信息。对每个执行的测试用例,会显示其名称和结果。
  • -rsxX 分别表示显示测试结果的简要信息、显示跳过的测试、显示出错的测试和显示预期失败的测试;
  • -l 表示显示每个测试的结果一行;
  • --tb=short 表示在测试失败时只显示简短的回溯信息。
  • --reruns=1:表示对测试失败的用例重新运行一次。如果某个测试用例在第一次运行时失败了,Py test会自动再次运行该用例。
  • --count=2:表示运行测试用例的次数。每个测试用例将被运行两次。
  • --html=./result/reports.html:表示生成HTML格式的测试报告,并将其保存为reports.html文件。
  • --self-contained-html:表示生成的HTML测试报告是自包含的,即在单个HTML文件中包含了所有相关的资源(CSS、Java Scrip等),可以单独打开和查看

加了addopts之后,我们在cmd中只需要敲pytest就可以生效了!!

4.3.4.log_cli

pytest提供了一个名为log_cli的命令行选项,用于控制测试日志的输出。

  • 作用:控制台实时输出日志
  • 格式:log_cli=True 或False(默认),或者log_cli=1 或 0

在pytest.ini 中添加如下:

[pytest]
addopts = -vs -rsxX -l --tb=short --reruns=1 -count=2 --html=./result/report.html --self-contained-html
markers =
    slow: mark a test as slow
    smoke: mark a test as smoke
    api: mark a test as an API test
log_cli=1

执行后如下:

Pytest测试用例执行_重新运行_11

注意:加了log_cli=1之后,可以清晰看到哪个package下的哪个module下的哪个测试用例是否passed还是failed;所以平时测试代码是否有问题的情况下推荐添加上加,但如果拿去批量跑测试用例的话不建议加,会影响运行性能。

4.3.5.norecursedirs

norecursedirs 是 pytest 的一个配置选项,用于告诉 pytest 在递归查找测试文件时要忽略的目录。这对于排除特定目录下的测试文件非常有用,以避免重复运行测试或不必要的测试。

以下是一个在 pytest.ini 文件中使用 norecursedirs 的示例,

  1. 假设你的项目的结构如下:
project/
├── tests/
│   ├── test_folder/
│   │   ├── test_file1.py
│   │   └── test_file2.py
│   └── test_file3.py
└── pytest.ini
  1. 在 pytest.ini 文件中,添加以下内容:
[pytest]
norecursedirs = test_folder

上述配置告诉 pytest 在搜索测试文件时忽略名为 test_folder 的目录。

  1. 确保在 pytest.ini 文件所在的目录中执行 pytest 命令。

现在,当你运行 pytest 命令时,pytest 将会递归搜索 tests 目录下的测试文件,但会忽略名为 test_folder 的目录中的测试文件。

请注意,norecursedirs 配置选项接受一个逗号分隔的目录列表,可以同时指定多个需要忽略的目录。例如,norecursedirs = test_folder, docs 将同时忽略 test_folder 和 docs 目录中的测试文件。

在 pytest.ini 中配置 norecursedirs 选项可以方便地一次性定义要忽略的目录,而不必在每次运行 pytest 命令时都手动指定 --ignore 选项。

4.3.6.更改测试用例收集规则

pytest默认的测试用例收集规则

  • 文件名以 test_*.py 文件和 *_test.py
  • 以  test_ 开头的函数
  • 以  Test 开头的类,不能包含 __init__ 方法
  • 以  test_ 开头的类里面的方法

可以修改或者添加这个用例收集规则的,是建议在原有的规则上添加的,在 pytest.ini 文件中使用 testpathspython_filespython_classespython_functions 是用来筛选测试文件、测试类和测试函数的选项。

  • testpaths:用于指定测试文件所在的路径。可以接受一个逗号分隔的目录列表,用来指定要搜索测试文件的目录。例如,testpaths = ./test_case 将会在 ./test_case 目录下搜索测试文件。
  • python_files:用于指定测试文件的命名格式。可以接受一个通配符格式的字符串,比如 test_*.py。这个选项告诉 pytest 只运行文件名符合 test_*.py 格式的测试文件。
  • python_classes:用于过滤测试类。可以接受一个通配符格式的字符串,比如 Test*。这个选项告诉 pytest 只运行以 Test 开头的测试类。
  • python_functions:用于过滤测试函数。可以接受一个通配符格式的字符串,比如 test*。这个选项告诉 pytest 只运行以 test 开头的测试函数。

这些选项的作用是帮助你在运行测试时筛选出指定的测试文件、测试类和测试函数,以便更精确地控制哪些测试要被运行。

例如,假设你的测试文件都位于 ./test_case 目录下,且文件名以 test_ 开头,测试类以 Test 开头,测试函数以 test 开头,你可以在 pytest.ini 文件中进行如下配置:

[pytest]
addopts = -vs -rsxX -l --tb=short --reruns=1 -count=2 --html=./result/report.html --self-contained-html
markers =
    slow: mark a test as slow
    smoke: mark a test as smoke
    api: mark a test as an API test
log_cli=1
xfail_strict = True
testpaths = ./test_case
python_files = test_*.py
python_classes = Test*
python_functions = test*

这样配置后,当你运行 pytest 命令时,pytest 将只运行满足上述条件的测试文件、测试类和测试函数。这些选项的使用可以让你更有效地管理测试文件和测试用例,将关注点更集中在你要测试的特定部分上,提高测试运行的效率。