单元测试框架Pytest

pytest是第三方库,使用前要先进行安装 pip install pytest

pytest单元测试框架的核心功能:

  • 收集用例:进入目录,运行pytest
  • 指定用例函数的定义规则:要以test开头的用例,支持普通函数,测试类
  • 自动执行用例:如果不能识别,需要修改pycharm默认的执行器改成pytest
  • 前置和后置夹具
  • 断言:pytest直接用关键字assert 表达式
  • 生成测试报告 :pytest  --html=报告名称

1.编写测试用例

 pytest用例编写规则很灵活:

  1. 直接定义函数,不需要测试类
  2. 编写测试类,不继承 unittest.TestCase
  3. 编写测试类,继承 unittest.TestCase,可以直接迁移(兼容)

 

 前两种方法前面没有运行播放键,所以我们需要去修改pycharm默认的执行器

点击【File】-->【Settings】-->【Tools】-->【Python Integrated Tools】-->

找到【Default test runner】点击下拉框选择pytest

设置完成后再查看,三种方法前面都有运行播放键

 

2.执行测试用例

2.1命令执行测试用例

相比unittest来说,pytest不用编写特意收集用例的代码,进入相应目录执行命令即可

进入项目的目录执行运行用例时,收集的是整个项目下所有的用例,而进入指定的cases包下执行运行用例时,收集的用例只量这个cases包下的用例数量

 2.2代码执行测试用例

run.py模块位置放在哪,执行的就是这个包下所有以test开头的模块的测试用例

修改run.py代码如下:(根目录下的run.py)

import pytest

# 收集并且运行用例
pytest.main()

 运行结果:

修改run.py代码如下:(cases目录下的run.py)

运行结果:

3.断言

pytest中没有assertEqul,只有aseert,这个是经过pytest进行了封装后的关键字,相比unittest使用简单,不用写self

4.测试报告

4.1命令执行测试报告

pytest-html 第三方库,使用需要先进行安装 pip install pytest-html

直接在命令行内输入pytest  --html=报告名称,可以自定义目录及报告名称,报告的样式没有unittest的美观,后面会介绍另一种报告的方法,相对样式要美观

 

4.2代码执行测试报告

import pytest
from datetime import datetime

# 获取时间戳
ts = datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
# 拼接文件目录
filename = f'reports/report-{ts}.html'
# 收集并且运行用例
pytest.main([f'--html={filename}'])

运行结果:

 

5.前置和后置夹具

pytest 和 unittest是可以兼容的,所以之前使用unittest编写的代码不用修改,直接使用pytest命令直接运行,如果没继承的话如何使用呢,新建test_pytest_fixture.py代码如下:

import pytest

# 声明这是一个夹具,这个夹具就是个函数
@pytest.fixture()
def fixt():
    # 这里相当于setup
    print('正在执行前置条件')
    # yield之前就是前置,yield之后就是后置
    yield
    # 这里相当于tearDown
    print('正在执行后置清理')


class TestFixture:
    def test_fixture(self, fixt):
        assert 1 + 1 == 2


class TestFixture1:
    def test_fixture1(self, fixt):
        assert 1 + 1 == 2

运行结果

夹具的封装:

新建fixtures.py,代码如下

import pytest


# 声明这是一个夹具,这个夹具就是个函数
@pytest.fixture()
def fixt():
    # 这里相当于setup
    print('正在执行前置条件')
    # yield之前就是前置,yield之后就是后置
    yield
    # 这里相当于tearDown
    print('正在执行后置清理')

修改test_pytest_fixture.py代码如下:

from common.fixtures import fixt


class TestFixture:
    def test_fixture(self, fixt):
        assert 1 + 1 == 2


class TestFixture1:
    def test_fixture1(self, fixt):
        assert 1 + 1 == 2

运行结果:

共享fixture:

  1. 将所有的夹具全部放到一个固定的模块文件,conftest.py
  2. 所有导入夹具的操作就可以省略了,pytest运行时会自动在conftest.py中查找

修改fixtures.py为conftest.py,代码不用修改,再次运行test_pytest_fixture.py结果如下图

注意:conftest.py要跟测试用例存放在同一个目录下,否则会报错

6.fixture作用域

pytest有非常灵活的作用域管理,有以下几个级别:

  • function 每个函数/方法运行一次 
  • class 每个类运行一次
  • module 每个模块运行一次
  • package 每个包运行一次
  • session 每个传话运行一次

项目中通常使用function和class级别多一些,其他的了解一下即可 

修改conftest.py代码如下:

import pytest


# 声明这是一个夹具,这个夹具就是个函数
@pytest.fixture()
def fixt():
    # 这里相当于setup
    print('正在执行前置条件')
    # yield之前就是前置,yield之后就是后置
    yield
    # 这里相当于tearDown
    print('正在执行后置清理')


@pytest.fixture(scope='function')
def function_fixt():
    print('function')
    yield
    print('function finished')


@pytest.fixture(scope='class')
def class_fixt():
    print('class')
    yield
    print('class finished')


@pytest.fixture(scope='module')
def module_fixt():
    print('module')
    yield
    print('module finished')

注意:function级别,可以默认不写,上面的fixt和fnuction_fixt一个没有一个写了,但都表示是function级别

修改test_pytest_fixture.py代码如下:

class TestFixture:
    def test_fixture(self, function_fixt,class_fixt,module_fixt):
        assert 1 + 1 == 2

    def test_fixture1(self, function_fixt,class_fixt,module_fixt):
        assert 1 + 1 == 2


class TestFixture2:
    def test_fixture21(self, function_fixt,class_fixt,module_fixt):
        assert 1 + 1 == 2

    def test_fixture22(self, function_fixt,class_fixt,module_fixt):
        assert 1 + 1 == 2

运行结果:

pytest夹具还提供了一个参数,autouse:自动使用夹具

修改conftest.py代码如下:

import pytest


# 声明这是一个夹具,这个夹具就是个函数
@pytest.fixture()
def fixt():
    # 这里相当于setup
    print('正在执行前置条件')
    # yield之前就是前置,yield之后就是后置
    yield
    # 这里相当于tearDown
    print('正在执行后置清理')


@pytest.fixture(scope='function', autouse=True)
def function_fixt():
    print('function')
    yield
    print('function finished')


@pytest.fixture(scope='class', autouse=True)
def class_fixt():
    print('class')
    yield
    print('class finished')


@pytest.fixture(scope='module', autouse=True)
def module_fixt():
    print('module')
    yield
    print('module finished')

修改test_pytest_fixture.py代码如下:

class TestFixture:
    def test_fixture(self):
        assert 1 + 1 == 2

    def test_fixture1(self):
        assert 1 + 1 == 2


class TestFixture2:
    def test_fixture21(self):
        assert 1 + 1 == 2

    def test_fixture22(self):
        assert 1 + 1 == 2

运行结果:

总结:

  1.  autouse不要随便使用,以免夹具搞混乱
  2. 使用了autouse后,方法/函数中就不用填写调用夹具的参数了

7.参数化

相当于unittest中的ddt

import pytest

data = [1, 2, 3]


class TestParams:
    @pytest.mark.parametrize('info', data)
    def test_params(self, info):
        print(info)
        assert 1 + 1 == 2

运行结果:

注意:在pytest中使用pamaretrize或fixture时,就不能使用unittest,因为参数化跟夹具是不兼容unittest的

实践当中会有两种模式:

模式1:

  1. 使用unittest编写测试用例,使用ddt和夹具setup
  2. 使用pytest运行

模式2:

全部使用pytest,完全舍弃unittest

总结:unittest是内置库,跟python版本走,稳定性高,而pytest是第三方库,环境要求高,版本不匹配的话容易出问题,所以使用模式1是很常见的

8.用例筛选

筛选步骤:

步骤0:提前把标记名注册到pytest中(这步可以选择不做)

步骤1:在测试类或方法上做一个标记/标签

步骤2:在运行用例时,通过标记执行

import pytest


class TestMark:
    @pytest.mark.success
    def test_mark(self):
        assert 1 + 1 == 2

    def test_mark1(self):
        assert 1 + 1 == 2

 运行结果:

如果觉得麻烦还可以在整个类上声明一下

import pytest


@pytest.mark.success
class TestMark:

    def test_mark(self):
        assert 1 + 1 == 2

    def test_mark1(self):
        assert 1 + 1 == 2

运行结果: 

注意:

  1. 标记名可以随便命名
  2. 用例筛选必须使用命令去运行,不支持界面点击播放臽或运行run.py
  3. 在参数化中不可以使用
  4. 在运行结果中我们可以看到有警告信息,那是因为没有在pytest中注册标记名

注册标记名:

点击运行结果中带mark.html的链接,跳转到Marking test functions with attributes — pytest documentation网站

这里提供了两种方法注册标记名,直接复制代码设置即可

 新建一个pytest.ini文件,编写代码后,运行

新建一个pyproject.toml文件,编写代码后,运行

 注册标记后,我们从运行结果可以看出,不显示警告信息了

mark支持逻辑运算and,or,not

进入相应目录下,执行pytest -m "success and login"

 进入相应目录下,执行pytest -m "success or login"

 进入相应目录下,执行>pytest -m "success and not login"

​​​​​​​

猜你喜欢

转载自blog.csdn.net/weixin_40611700/article/details/121283395