本文首发于:行者AI

Pytest是Python的一种单元测试框架,与unittest相比,使用起来更简洁、效率更高,也是目前大部分使用python编写测试用例的小伙伴们的第一选择了。

除了框架本身提供的功能外,Pytest还支持上百种第三方插件,良好的扩展性可以更好的满足大家在用例设计时的不同需求。本文将为大家详细介绍下面5项常用的插件。

1. 用例依赖

编写用例的时候,我们会注意用例之间的独立性,但部分用例之间确实存在关联,无法做到彻底独立,那么我们就可以通过使用插件pytest-dependency设置用例之间的依赖关系。当用例A依赖于用例B时,若用例B执行失败,则用例A将会自动跳过不执行。如此,就可以避免去执行一个必定会失败的用例,相当于pytest.mark.skip。

(1)安装:

pip install pytest-dependency

(2)使用说明:

首先,在标记被依赖用例时,需要在被依赖的用例上添加装饰器pytest.mark.dependency(),且被依赖用例需要在关联用例前执行。也可以给被依赖用例设置别名,通过添加参数name实现。

在关联的依赖用例上,同样需要添加装饰器pytest.mark.dependency(depends=['用例名称']),与之前不同的是,装饰器必须要填写depends参数完成用例的关联,关联的被依赖用例存在多个时可以使用“,”隔开。

此外,还可以通过scope参数指定用例依赖的范围,同样是session、package、module、class这四种类型,此处不详细展开。

具体通过下方的示例以及执行结果来进一步说明。

(3)示例及执行结果分析

示例:

import pytest


class TestCase:

    # 通过装饰器@pytest.mark.dependency()标记当前用例为被依赖用例,被依赖用例需要优先关联用例执行
    @pytest.mark.dependency()
    def test_01(self):
        print("测试用例01,执行失败")
        assert 1 == 2

    # 通过使用装饰器关联被依赖用例,通过depends参数指定用例名称关联用例
    @pytest.mark.dependency(depends=['test_01'])
    def test_02(self):
        print("测试用例02,跳过")

    # 标记被依赖用例时,可以通过name参数指定别名
    @pytest.mark.dependency(name="func_2")
    def test_03(self):
        print("测试用例03,执行成功!")

    # 使用depends参数指定定义的别名关联用例
    @pytest.mark.dependency(depends=['func_2'])
    def test_04(self):
        print("测试用例04,执行成功!")

    # depends参数可以关联多个测试用例,使用“,”分隔即可
    @pytest.mark.dependency(depends=['test_01', 'func_2'])
    def test_05(self):
        print("测试用例05,跳过")


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

执行结果如下:

我们可以看出,只有依赖用例执行成功时,当前用例才会被执行,否则会被跳过。依赖多个用例时,只有全部成功,才会执行,否则一样会跳过。

2. 失败重跑

有些情况下,用例在执行过程中可能会受到一些客观因素的影响,导致用例执行失败,通过使用pytest-rerunfailures插件,可以在失败后重新执行用例,并设置重新运行的最大次数。以此保证用例执行结果的准确性。

(1)安装:

pip install pytest-rerunfailures

(2)使用说明:

失败重跑共有两种使用方式,分别是通过装饰器执行和命令行执行。

使用装饰器时,需要在用例上添加装饰器pytest.mark.flaky(reruns=重新执行最大次数, reruns_delay=执行间隔时间(单位:秒)),在执行过程中,添加了装饰器的用例在执行失败后会按照设置的次数和时间重新执行。

通过在命令行执行时,同样需要指定"rerun"和"rerun-delay"两个参数来实现,如:pytest --reruns 重新执行最大次数 --reruns-delay 间隔时间。

注意:reruns是重新执行的最大次数,如果在达到这一数量前用例执行成功,则不会继续重跑,判断用例执行通过;否则执行到最大次数后,用例仍失败,则判断用例执行失败。

具体通过下方的示例和执行结果进一步说明。

(3)示例及执行结果分析

示例:

import pytest
import random


class TestCase:
    
    # 使用装饰器设置用例失败后的重新执行最大次数和每次执行的间隔时间(单位:秒)
    @pytest.mark.flaky(reruns=3, reruns_delay=1)
    def test_01(self):
        result = random.choice(['a', 'b', 'c', 'd', 'e'])
        print(f"result={result}")
        assert result == 'c'


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

执行结果如下:

我们可以看出,当用例断言失败后,会重新执行,直到达到设置的最大次数或执行成功为止。

3. 指定用例执行顺序

pytest在执行用例的时候,默认是按照文件中用例的先后顺序执行,有时我们可能在维护测试用例时遇到需要修改用例执行顺序的情况,但是如果每次都通过修改大段的用例代码先后位置来控制,并不利于维护。因此,使用插件pytest-ordering可以快速实现用例执行顺序的设置,后期维护时,也只需要修改对应的执行顺序参数即可。

(1)安装:

pip install pytest-ordering

(2)使用说明:

通过给用例添加装饰器pytest.mark.run(order=执行顺序)设置用例的执行顺序。在执行的时候,使用装饰器pytest.mark.run的用例会优先没有装饰器的用例执行,设置了执行顺序的用例则按照order参数设置的大小升序执行。

具体通过下方的示例和执行结果进一步说明。

(3)示例及执行结果分析

示例:

import pytest


class TestCase:

    def test_01(self):
        print("测试用例01")

    def test_02(self):
        print("测试用例02")
    
    # 使用装饰器设置执行顺序为2
    @pytest.mark.run(order=2)
    def test_03(self):
        print("测试用例03")
    
    # 使用装饰器设置执行顺序为1
    @pytest.mark.run(order=1)
    def test_04(self):
        print("测试用例04")


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

执行结果:

我们可以看出,执行的顺序和预期一致。优先执行标明了执行顺序的用例,并按照order的值由小到大执行。

4. 分布式运行

当项目的用例很多的时候,执行通常会耗时颇久,通过分布式运行,则可以大量缩短整体用例的执行时间。pytest-xdist插件就可以帮助我们完成测试用例的分布式运行。

(1)安装:

pip install pytest-xdist

(2)使用说明:

在命令行执行用例时,通过参数-n设置并行启动的进程数量。除了设置具体的数量外,还可以设置为auto,这种情况下,会依据当前设备的cpu数量执行。

此外,还可以通过--dist参数,设置用例分组,同一个组内的用例会在同一个进程中执行。

  • --dist=loadscope 同一个module或同一个class下的用例会分配为同一组,按class分组优先于module。
  • --dist=loadfile 同一个.py文件中的用例会分配为同一组。

具体通过下方的示例和执行结果进一步说明。

(3)示例及执行结果分析

示例:

import pytest
from time import sleep


class TestCase1:

    @pytest.mark.parametrize('keyword', ['a', 'b', 'c', 'd', 'e',
                                         'f', 'g', 'h', 'i', 'j'])
    def test_baidu_search(self, keyword):
        sleep(1)
        print(f'搜索关键字{keyword}')


class TestCase2:

    @pytest.mark.parametrize('user', ['user1', 'user2', 'user3', 'user4', 'user5',
                                      'user6', 'user7', 'user8', 'user9', 'user10'])    def test_login(self, user):
        sleep(1)
        print(f'用户{user}登录成功')


if __name__ == '__main__':
    # pytest.main(['-vs']) # 不使用pytest-xdist运行
    pytest.main(['-vs', '-n', '2']) # 使用pytest-xdist运行

执行结果:

从上方的两次执行结果中可以看出,使用分布式运行后,用例的运行时间明显缩短。示例中的用例彼此之间没有关联,如果实际使用时用例之间存在依赖关系,可以使用--dist参数为用例分组,确保关联的用例在同一组内。

5. 多重断言

有时,在一个用例中,我们需要对结果进行不同维度的多个断言,但是使用assert断言时,只要有一个断言失败,后续的断言就不会继续执行。现在,我们可以通过使用pytest-assume插件来解决这个问题,当断言失败后,仍会继续执行后续的断言。

(1)安装:

pip install pytest-assume

(2)使用说明:

在用例中,把使用assert进行的断言,改为使用pytest.assume()进行断言即可。

具体通过下方的示例和执行结果进一步说明。

(3)示例及执行结果分析

示例:

import pytest


class TestCase:
    
    # 使用assert断言
    def test_01(self):
        print("断言1")
        assert 1 == 1
        print('断言2')
        assert 2 == 1
        print("断言3")
        assert 3 == 3
        print('用例结束')
    
    # 使用pytest.assume()断言
    def test_02(self):
        print('断言1')
        pytest.assume(1 == 1)
        print('断言2')
        pytest.assume(2 == 1)
        print('断言3')
        pytest.assume(3 == 3)
        print('用例结束')


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

执行结果:

从执行结果中可以看出,使用assert断言时,断言失败不会再执行后续的内容;而使用pytest.assume()断言时,断言失败仍会执行至用例结束。这样更有利于我们一次性获取用例执行中全部错误信息。

6. 小结

本文为大家介绍了一些常用的pytest框架的插件,可以帮助我们解决一些实际使用过程中遇到的问题。目前,pytest支持的插件已经多达868个,除了本文介绍的5个常用插件外,还有很多支持其它需求的插件,大家可以根据自己的需要尝试查找使用相关的插件,以便能够更好的设计出符合业务场景的测试用例。


行者AI
48 声望8 粉丝

行者AI(成都潜在人工智能科技有限公司)专注于人工智能在游戏领域的研究和应用,凭借自研算法,推出游戏AI、智能内容审核、数据平台等产品服务。


引用和评论

0 条评论