单元测试框架-pytest

发布时间 2023-09-25 13:36:19作者: 阿明明

1.简介

Pytest是基于python语言的单元测试框架,也是一个命令行工具,具有以下特点:

  • 入门简单,易上手
  • 支持大量的第三方插件,如:失败重试,控制用例执行顺序等
  • 基于配置文件可以简单的集成CI(持续集成)工具中

 

2.快速入门

安装

pip install pytest

 

 

基本格式

def add(x, y):
    return  x+y


class  TestAddFunc(object):  # 测试用例的类名必须以 Test 为开头
    def test_01(self):  # 方法名和函数名必须以 test 为开头
        print(add(10,20))

    def test_02(self):
        print(add(10, 20))

    def test_03(self):
        print(add(10, 20))

 

测试运行

pytest中提供了三种方式给测试 人员运行测试用例

  1. 命令行运行
    pytest -s  -v 文件名
    
    # -s:输出测试用例中的print打印的信息
    # -v: 输出测试用例的类名和方法名
    # -x: 一旦发现测试用例失败,立即停止运行
    # no:warnings: 不显示警告

     

     

     

  2. Pycharm运行
    运行测试测试文件即可

  3. mian函数运行
    # pytest.main(["模块文件名::类名::方法名字","参数"])
    pytest.main(["./demo/pytest_01_基本格式","-sv"])
    # pytest.main(["./demo/pytest_01_基本格式::TestAddFunc","-sv"])
    # pytest.main(["./demo/pytest_01_基本格式::TestAddFunc::test_01","-sv"])

     

测试脚手架

  • 方法级别 :setup 和 teardown    ----> 每个用例都会执行
  • 类级别: setup_class 和 teardown_class  ----> 每个测试类都会执行
  • 模块级别: setup_module 和 teardown_module  ----> 每个测试模块都会执行
import pytest


def add(x, y):
    return x + y


def setup_module():
    print("模块初始化")


def teardown_module():
    print("模块结束")


class TestAddFunc(object):  # 测试用例的类名必须以 Test 为开头

    def setup_class(self):
        print("类初始化")

    def teardown_class(self):
        print("类结束")

    def setup(self):
        print("用例初始化")

    def teardown(self):
        print("用例结束")

    def test_01(self):  # 方法名和函数名必须以 test 为开头
        print(add(10, 20))

    def test_02(self):
        print(add(10, 20))

    def test_03(self):
        print(add(10, 20))


if __name__ == '__main__':
    # pytest.main(["模块文件名::类名::方法名字","参数"])
    pytest.main(["./demo/pytest_01_基本格式", "-sv"])
    # pytest.main(["./demo/pytest_01_基本格式::TestAddFunc","-sv"])
    # pytest.main(["./demo/pytest_01_基本格式::TestAddFunc::test_01","-sv"])

以上的执行顺序:

 

基于配置文件运行pytest

在pytest提供的终端运行测试用例的方法上,pytest也支持使用配置文件来指定运行的参数,常用的配置文件名为

  • pytest.ini
  • tox.ini
  • setup.cfg

配置文件一般保存在项目的根目录下

 

pytest.ini

[pytest]
# 指定运行参数
addopts = -s -v

# 搜索测试文件的目录路径
testpaths = ./

# 搜索测试文件名格式
python_files = test_*.py

# 搜索测试类格式
python_classes = Test*

# 搜索测试方法名格式
python_functions = test_*

 

运行用例

pytest

 

断言

pytest中的断言就是python中的关键字assert

格式

assert 表达式, 断言错误提示信息
import pytest


def add(x, y):
    return  x+y


class  TestAddFunc(object):  # 测试用例的类名必须以 Test 为开头
    def test_01(self):  # 方法名和函数名必须以 test 为开头
        res = add(10,20)
        assert res == 30,"两者不相等"

    def test_02(self):
        res = add(10, 20)
        assert type(res) is str, "数据类型不是字符串"

 

跳过

根据特定的条件,不执行标识的测试函数

@pytest.mark.skipif(判断条件, reson="跳过原因")
import pytest

version = (2,1,2)
def add(x, y):
    return  x+y


class  TestAddFunc(object):
    def test_01(self):
        res = add(10,20)
        assert res == 30,"两者不相等"

    @pytest.mark.skipif(version>(2,1,1),reason="版本太低,不测试")
    def test_02(self):
        res = add(10, 20)
        assert type(res) is str, "数据类型不是字符串"

 

提示:该跳过装饰器可以添加在函数上也可以添加在类上面

 

参数化

参数化可以将多个测试用例简化为一个或者几个测试函数,说白了就是多个测试用例具有相同的测试参数

import pytest

version = (2,1,2)
def add(x, y):
    return  x+y


class TestAddFunc(object):
    @pytest.mark.parametrize("args",[{'x':10, "y":20,}, {'x':'10', "y":'20',}, {'x':20, "y":20,}])
    def test_start(self, args):
        res = add(**args)
        print(res)
        assert res == 30,"两者不相等"

 

3.进阶使用

fixture

fixture有个scope的参数,可以控制fixture的作用范围(从大到小):session>mudule>class>function

session:多个文件调用一次,可以跨.py文件调用
mudule: 每一个.py调用一次
class: 每个类调用一次
function:每一个方法或者函数调用一次

 

  • 实现参数化效果
    简单使用
    import pytest
    
    @pytest.fixture(scope="class")
    def fixture_data():
        print("运行fixture")
        return 10, 20
    
    def add(x, y):
        return  x+y
    
    class  TestAddFunc(object):
        def test_01(self, fixture_data): # 参数名字就是脚手架的名字,必须保持一致
            res = add(fixture_data[0],fixture_data[1])
            assert res == 30,"两者不相等"
    
        def test_02(self, fixture_data):
            res = add(fixture_data[0],fixture_data[1])
            assert res == 30,"两者不相等"

     多个脚手架使用

    import pytest
    
    @pytest.fixture(scope="class")
    def fixture_data_01():
        print("运行fixture-01")
        return 10, 20
    
    
    @pytest.fixture(scope="class")
    def fixture_data_02():
        print("运行fixture-02")
        return 10, 30
    
    def add(x, y):
        return  x+y
    
    
    # 可以显示的指明调用的fixture,不加也是可以的,主要是便于阅读代码
    @pytest.mark.usefixtures("fixture_data_01")
    @pytest.mark.usefixtures("fixture_data_02")
    class  TestAddFunc(object):
        def test_01(self, fixture_data_01):
            res = add(fixture_data_01[0],fixture_data_01[1])
            assert res == 30,"两者不相等"
    
        def test_02(self, fixture_data_02):
            res = add(fixture_data_02[0],fixture_data_02[1])
            assert res == 40,"两者不相等"

     

  • 自动执行
    可以实现类似setup和teardown的作用,在执行用例之前或者之后运行
    import pytest
    
    # 在每一个测试用例执行之前执行 @pytest.fixture(scope
    ="function", autouse=True) def fixture_data_01(): print("运行fixture-01") return 10, 20 # 不会自动执行 @pytest.fixture(scope="function") def fixture_data_02(): print("运行fixture-02") return 10, 30 def add(x, y): return x+y class TestAddFunc(object): def test_01(self): pass def test_02(self): pass

     

  • yied使用
    在上面的案例中我们可以实现类似setup的作用,即可以在用例执行之前运行,那么如何实现teardown的作用呢?就可以使用yield的关键字
    说的更直白一点就是yied关键字可以实现teardown和setup的集合,同时yied的返回值可以充当用例的执行入参,非常类似python中的上下文操作

    import pytest
    
    @pytest.fixture(scope="function", autouse=True)
    def fixture_data_01():
        print("开始运行fixture-01")
        yield 10
        print("结束运行fixture-01")
    
    
    def add(x, y):
        return  x+y
    
    
    
    class  TestAddFunc(object):
        def test_01(self, fixture_data_01):
            print(fixture_data_01)
            pass
    
        def test_02(self):
            pass

  • fixture代码分离
    我们可以将fixture的代码写好,放在一个名为conftest.py文件中,可以被pytest自动识别,进而实现了测试用例和fixture的分离
    conftest.py应该和测试代码放在同一个目录下,在实际开发中可以有多个conftest.py文件,每个文件的作用域是当前自身所在的当前目录及其子目录

    conftest.py 代码
    import pytest
    
    @pytest.fixture(scope="function", autouse=True)
    def fixture_data_01():
        print("开始运行fixture-01")
        yield 10
        print("结束运行fixture-01")
    

    测试用例代码

    def add(x, y):
        return  x+y
    
    
    
    class  TestAddFunc(object):
        def test_01(self, fixture_data_01):
            print(fixture_data_01)
            pass
    
        def test_02(self):
            pass

第三方组件的使用

  • 控制用例执行顺序
    pytest默认执行用例的顺序是按照源代码的上下顺序执行的,如果希望控制执行顺序,可以通过第三方组件pytest-ordering实现

    安装
    pip install pytest-ordering


    使用
    执行顺序为:优先执行正序排序的方法,在执行没有排序的方法,最后执行负数排序的方法,如果多个方法都是正序,则先执行排序小,负数亦然

    import  pytest
    
    class TestAdd(object):
        @pytest.mark.run(order=-1)
        def test_01(self):
            print("test_01")
    
        @pytest.mark.run(order=-2)
        def test_02(self):
            print("test_02")
    
        def test_03(self):
            print("test_03")
    
        @pytest.mark.run(order=1)
        def test_04(self):
            print("test_04")
    
        @pytest.mark.run(order=2)
        def test_05(self):
            print("test_05")


    注意:pytest-ordering组件不能和fixture一起使用,会报错的,如果在使用pytest_ordering的情况下还需要参数化,可以使用@pytest.mark.parameterze

  • 失败用例重试
    针对网络场景和服务端性能不稳定的情况下,进行测试用常常发生用例运行失败的情况,我们可以指定重试次数,已达到重试更加准确的结果

    安装
    pip install pytest-rerunfailures 


    使用

    在执行pytest中添加执行参数即可
    
    --reruns n :重试次数
    --reruns-delay m:(重试间隔)

    全局失败用例重试
    配置文件pytest.ini

    [pytest]
    # 指定运行参数
    addopts = -s -v -p no:warnings --reruns 3 --reruns-delay 2
    
    # 搜索测试文件的目录路径
    testpaths = ./
    
    # 搜索测试文件名格式
    python_files = pytest_*.py
    
    # 搜索测试类格式
    python_classes = Test*
    
    # 搜索测试方法名格式
    python_functions = test_*

    不通过的用例会最多执行3次,每次重试间隔2秒,最后返回结果


    局部失败用例测试

    import pytest
    
    
    def add(x, y):
        return  x+y
    
    
    
    class  TestAddFunc(object):
        def test_01(self, fixture_data_01):
            print(fixture_data_01)
            pass
    
        @pytest.mark.flaky(reruns=4, reruns_delay=2)
        def test_02(self):
            assert 1== 2

     
    注意:
    局部参数会覆盖全局参数,也就是说使用了局部参数,全局用法就会失效
    与pytest.fixture脚手架也会存在冲突,不能混合使用

  • 用例并发运行
    当测试用例非常多的时候,一条条执行是很浪费时间的。如果测试用例之间彼此独立,没有任何依赖关系,就可以并发运行用例,从而节省时间
    pytest-xdist可以实现并发运行,属于进程级别的

    安装
    pip install pytest-xdist


    使用
    在运行测试用例的时候添加一个参数即可,可以指定运行测试的时候所开启的进程数量

    -n 4 :使用4个进程运行,
    -n auto : 自动检测CPU核数,根据CPU核数创建对应数量的进程个数


    pytest.ini代码

    [pytest]
    # 指定运行参数
    addopts = -s -v -p no:warnings --reruns 3 --reruns-delay 2 -n 4
    
    # 搜索测试文件的目录路径
    testpaths = ./
    
    # 搜索测试文件名格式
    python_files = pytest_*.py
    
    # 搜索测试类格式
    python_classes = Test*
    
    # 搜索测试方法名格式
    python_functions = test_*

    测试用例

    import  pytest
    
    class TestAdd(object):
        @pytest.mark.run(order=-1)
        def test_01(self):
            print("test_01")
    
        @pytest.mark.run(order=-2)
        def test_02(self):
            print("test_02")
            assert 1 ==1
    
        def test_03(self):
            print("test_03")
    
        @pytest.mark.run(order=1)
        def test_04(self):
            print("test_04")
    
        @pytest.mark.run(order=2)
        def test_05(self):
            print("test_05")

    运行结果

     

  • 生成HTML格式测试用例
    生成的测试报告很简单,在实际的开发中,还是使用Allure来生成测试报告

    安装
    pip install pytest-html


    使用
    在执行测试用例的时候,添加参数  --html=生成测试报告路径  即可
    pytest.ini文件

    [pytest]
    # 指定运行参数
    addopts = -s -v -p no:warnings --reruns 3 --reruns-delay 2 -n 4 --html=report.html
    
    # 搜索测试文件的目录路径
    testpaths = ./
    
    # 搜索测试文件名格式
    python_files = pytest_*.py
    
    # 搜索测试类格式
    python_classes = Test*
    
    # 搜索测试方法名格式
    python_functions = test_*