一、pytest介绍
pytest是一个常用的python测试框架,它提供了丰富的功能和灵活的用法,使得编写和
运行测试用例变得简单而高效。其他的测试框架还有unittest、robot framework等。
1.pytest的优点
- 语法简洁:编写测试用例非常友好。
- 支持参数化测试:允许使用不同的参数多次运行同一个测试函数,提高了测试效率。
- 丰富的插件生态系统:可以通过插件扩展各种功能,覆盖率测试、测试报告生成、失败用例重复执行等。此外pytest还支持与selenium、requests、appinum等结合,实现Web自动化、接口自动化、App自动化测试。
- 灵活性高:fixture机制替代固定的setUp/tearDown,支持多作用域、按需复用,适配复杂测试场景。
2.pytest的安装
python
pip install pytest
3.pytest的运行规则
- 文件名:必须以test_开头或者_test结尾
- 类名:必须以Test开头,并且不能有__init__方法
- 方法名:必须以test开头
为什么测试类中不能出现__init__方法?
- pytest 实例化测试类时,默认不会传递任何参数:pytest会为每个测试方法创建一个独立的测试类实例,且调用构造方法时不带任何参数;
- 如果自定义
__init__,会破坏实例化逻辑 :一旦定义了带参数的__init__,pytest实例化时会因缺少参数报错;即使是无参的__init__,也可能覆盖pytest注入的核心功能(比如 fixture 上下文)。
4.pytest的参数命令
|---------------------------|-------------------|--------------------|
| 命令 | 描述 | 备注 |
| pytest | 运行当前目录及其子目录里所有的测试 | |
| pytest -s | 显示测试中的print语句 | |
| pytest -v | 增加输出的详细程度 | |
| pytest 文件名 | 只运行指定的测试文件 | |
| pytest 路径名 | 运行指定目录下所有的测试 | |
| pytest -k "关键词" | 执行名称包含关键词的测试 | |
| pytest 文件名::测试类::测试方法 | 精准执行单个测试方法 | |
| pytest --html=report.html | 生成HTML格式的测试报告 | 需要安装pytest-html插 件 |
| pytest --cov | 测量测试覆盖率 | 需要安装pytest-cov插 件 |
二、pytest的配置文件
在了解了pytest的参数命令后我们可以发现,每次运行pytest时都要重新输入这些参数,为了避免参数命令过长以及重复的问题,我们可以将这些参数统一放到一个配置文件中。
这个配置文件就是pytest.ini文件,该文件文件通常位于项目的根目录下。通过在pytest.ini中定义配置项,可以覆盖pytest的默认行为,以满足项目的需求。
以下为常见的配置选项:
|------------------|-------------------------|
| 参数 | 解释 |
| addopts | 指定在命令行中默认包含的选项 |
| testpaths | 指定搜索测试的目录 |
| python_files | 指定发现测试模块时使用的文件匹配模式 |
| python_classes | 指定发现测试类时使用的类名前缀或模式 |
| python_functions | 指定发现测试函数和方法时使用的函数名前缀或模式 |
| norecursedirs | 指定在搜索测试时应该避免递归进入的目录模式 |
| markers | 定义测试标记,用于标记测试用例 |
示例:详细输出cases的文件名下以test_ 开头且方法名以Test 开头的所有用例
python
[pytest]
addopts = -vs
testpaths = ./cases
python_files = test_*.py
python_classes = Test*
# 这样配置好后,只需要在终端输入pytest即可
三、pytest的断言
断言(assert)是一种调试辅助工具,用于检查程序的状态是否符合预期,pytest可以使用Python中提供的assert语句来测试预期和值。
基本语法如下:
python
assert 条件, 错误信息
# 条件:必须是一个布尔表达式
# 错误信息:当条件为假时显示的错误信息,可选
示例:
python
#断言整数
a = 1
b = 2
assert a == b
#断言字符串
str = "hello"
assert "hello" == str
# 断言列表
expect_list = [1, 'apple', 3.14]
actual_list = [1, 'apple', 3.14]
assert expect_list == actual_list
# 断言元组
expect_tuple = (1, 'apple', 3.14)
actual_tuple = (1, 'apple', 3.14)
assert expect_tuple == actual_tuple
# 断言字典
expect_dict = {'name': 'Alice', 'age': 25}
actual_dict = {'name': 'Alice', 'age': 25}
assert expect_tuple == actual_tuple
# 断言集合
expect_set = {1, 2, 3, 'apple'}
actual_set = {1, 2, 3, 'apple'}
assert expect_set == actual_set
# 断言函数
def divide(a, b):
assert b != 0, "除数不能为0"
return a / b
print(divide(10, 2)) # 输出 5.0
print(divide(10, 0)) # 抛出 AssertionError: 除数不能为0
四、pytest的前后置
在前面我们说过,pytest的测试类中不能出现__init__方法,那么该如何进行初始化呢?
在测试框架中,前后置是指在执行测试用例前和测试用例后执行一些额外的操作,这些操作可以用于设置测试环境、准备测试数据等,以确保测试的可靠性
pytest提供了三种前后置的方法,适用于不同的情况:
- setup_method和teardown_method:这两个方法用于类中的每个测试方法的前置和后置操作
- setup_class和teardown_class:这两个方法用于整个测试类的前置和后置操作
- fixture:这是pytest推荐的方式来实现测试用例的前置和后置操作。fixture提供了更灵活的控制和更强大的功能。
1.setup_method和teardown_method

从图中我们可以看到,有两个方法,使用setup_method和teardown_method后这两个方法分别进行一次前后置
2.setup_class和teardown_class

从图中我们可以看到,有两个方法,使用setup_class和teardown_class后这一个类只进行一了次前后置
3.fixture
pytest中的fixture是一种强大的机制,用于提供测试函数所需的资源或上下文。它可以用于
设置测试环境、准备数据等。
注意:需要通过使用@pytest.fixture装饰器来告诉pytest一个特定函数是一个fixture
以下用两个例子进行展示,分别是使用fixture作为前置(初始化)和使用普通方法作为前置(初始化):
python
import pytest
# 使用fixture作为前置
@pytest.fixture
def fixture_01():
print('使用fixture进行前置....')
def test_1(fixture_01):
print('运行测试1')
# 使用普通方法作为前置
def fixture_02():
print('使用普通方法进行前置....')
def test_2():
fixture_02()
print('运行测试2')

结果可以看到,虽然都可以进行前置操作,但是前者可以将函数名作为参数进行调用,而后者需要在方法体中调用。
因此测试脚本中存在的很多重复的代码、公共的数据对象时,使用fixture最为合适。
1.fixture的嵌套
fixture可以进行嵌套使用,即fixture也可以使用其他的fixture
python
import pytest
@pytest.fixture
def fixture_01():
return "a"
@pytest.fixture
def fixture_02(fixture_01):
return [fixture_01]
# 调用fixture_02
def test_1(fixture_02):
fixture_02.append("b")
print(fixture_02)

2.请求多个fixture
一个方法中不但可以请求一个fixture,也可以请求多个
python
import pytest
@pytest.fixture
def fixture_01():
return "a"
@pytest.fixture
def fixture_02():
return ["a", "b", "c"]
# 调用fixture_01和fixture_02
def test_1(fixture_01, fixture_02):
assert fixture_01 in fixture_02

3.yield fixture
当我们运行测试时,我们希望确保它们能够自我清理,以便它们不会干扰其他测试(同时也避免留下大量测试数据来膨胀系统)。pytest中的fixture提供了一个非常有用拆卸系统,它允许我们为每个fixture 定义具体的清理步骤。
yield fixture使用yield而不是return 。有了这些fixture,我们可以运行一些代码,并将对象返回给请求的fixture/test,就像其他fixture一样。唯一的不同是:
• return 被替换为yield 。
• 该fixture 的任何拆卸代码放置在yield 之后。
一旦pytest确定了fixture的线性顺序,它将运行每个fixture直到它返回或yield,然后继续执行列表中的下一个fixture做同样的事情。
测试完成后,pytest将逆向遍历fixture列表,对于每个yield的fixture,运行yield语句之后的代码。

4.fixture的不同参数
pytest.fixture(scope='', params='', autouse='', ids='', name='')
参数详解:
scope ------ 参数用于控制fixture的作用范围,决定了fixture的生命周期。可选值有:
- function (默认):每个测试函数都会调用一次fixture。
- class :在同一个测试类中共享这个fixture。
- module :在同一个测试模块中共享这个fixture。(一个文件里)
- session :整个测试会话中共享这个fixture。
autouse ------ 参数默认为False 。如果设置为True ,则每个测试函数都会自动调用该fixture, 无需显式传入
params ------ 参数用于参数化fixture,支持列表传入。每个参数值都会使fixture执行一次,类 似于for循环
ids ------ 与params配合使用,为每个参数化实例指定可读的标识符(给参数取名字)
name ------ 参数用于为fixture显式设置一个名称。如果使用了name,则在测试函数中需要使 用这个名称来引用 fixture(给fixture取名字)
scope的用法
scope="function" :每个函数都会调用一次fixture
python
import pytest
@pytest.fixture(scope="function")
def fixture():
print("初始化....")
yield
print("清除数据....")
class TestExample:
def test_01(self, fixture):
print("第一个测试用例")
def test_02(self, fixture):
print("第二个测试用例")

scope="class" :一个类中调用一次fixture
python
import pytest
@pytest.fixture(scope="class")
def fixture():
print("初始化....")
yield
print("清除数据....")
class TestExample:
def test_01(self, fixture):
print("第一个测试用例")
def test_02(self, fixture):
print("第二个测试用例")

在使用scope="mould"和scope="session"时我们需要注意,因为这两个参数是作用在不同文件的情况下的,所以可以使用到conftest.py,conftest的介绍如下:
- conftest.py是一个单独存放的夹具配置文件,名称是固定的不能修改
- 可以在项目中的不同目录下创建多个conftest.py 文件,每个conftest.py 文件都会对其所在目录及其子目录下的测试模块生效
- 在不同模块的测试中需要用到conftest.py的前后置功能时,不需要做任何的import导入操作
- 作用:可以在不同的.py文件中使用同一个fixture函数
已知现在的结构如图所示:

这是scope="mould"和scope="session"的contest.py:
python
# scope="module"时的conftest.py
import pytest
@pytest.fixture(scope="module")
def fixture():
print("初始化....")
yield
print("清除数据....")
# scope="session"时的conftest.py
import pytest
@pytest.fixture(scope="session")
def fixture():
print("初始化....")
yield
print("清除数据....")
test_01.py和test_02.py因为代码相同,所以不再重复展示:
python
# test_01.py的代码
def test_case01(fixture):
print("单独放出来的测试用例01")
class TestCase01():
def test_01(self,fixture):
print("第一个测试用例")
def test_02(self,fixture):
print("第二个测试用例")
# test_02.py的代码
def test_case02(fixture):
print("单独放出来的测试用例02")
class TestCase02():
def test_01(self,fixture):
print("第一个测试用例")
def test_02(self,fixture):
print("第二个测试用例")
以下是scope="mould"和scope="session"的结果展示:


autouse的用法
autouse默认为False ,即当前的fixture需要手动显示调用,在该案例之前我们默认使用的都是autouse=False
当autouse=True时,fixture会在所有测试函数执行之前自动调用,无论这些测试函数是否显
式地引用了该fixture
python
import pytest
@pytest.fixture(scope="class",autouse=True)
def fixture():
print("初始化....")
yield
print("清除数据....")
class TestExample:
# 这里不再显示调用fixture
def test_01(self):
print("第一个测试用例")
# 这里不再显示调用fixture
def test_02(self):
print("第二个测试用例")
params的用法

五、pytest的参数化
参数化设计是自动化设计中的一个重要组成部分,它通过定义设计参数和规则,使得设计过程更加灵活和可控。
关于pytest的参数化,有两种实现方式,第一种就是通过fixture,fixture更适合需要动态数据和资源管理的复杂场景 ;第二种则是通过pytest.mark.parametrize装饰器,parametrize更适合单个的、简单的场景。
1.在用例上使用参数化

这里parametrize装饰器定义了三个不同的(input,result) 元组,以便test_add函数将依次使用它们运行三次。
2.在类上使用参数化

3.对整个文件使用参数化
要对模块中的所有测试进行参数化,你可以将pytestmark全局变量赋值:

4.自定义参数化

六、pytest的执行顺序的运行
在使用pytest进行测试时,有时候我们需要按照特定的顺序来运行测试用例,尤其是在涉及到测试用例之间的依赖关系时。
pytest本身并不直接支持通过配置来改变测试用例的默认运行顺序,pytest-order是一个第三方插件,专门用于控制测试用例的执行顺序。
安装该插件的方法如下:
python
pip install pytest-order
当你需要调整顺序时,只需要在测试用例上加上**@pytest.mark.order(你想要的顺序)**即可
