目录👑
pytest参数化设计@pytest.mark.parametrize
[7.1 基本使用](#7.1 基本使用)
[7.2 嵌套使用](#7.2 嵌套使用)
[7.4核心特性:yield fixture(前置+后置)](#7.4核心特性:yield fixture(前置+后置))
[7.5 fixture参数(灵活控制作用范围)](#7.5 fixture参数(灵活控制作用范围))
[7.6 全局fixture:conftest.py](#7.6 全局fixture:conftest.py)
pytest安装:版本匹配是关键
学习pytest的第一步就是安装,这里要注意版本兼容性,避免因版本问题导致后续使用异常。
安装命令很简单,在命令行输入以下代码即可:
pip install pytest==8.3.2
如果你的Python版本低于3.8,可以参考以下版本对应关系选择合适的pytest版本:
| pytest 版本 | 最低 Python 版本 |
|---|---|
| 8.0+ | 3.8+ |
| 7.1+ | 3.7+ |
| 6.2 - 7.0 | 3.6+ |
| 5.0 - 6.1 | 3.5+ |
| 3.3 - 4.6 | 2.7, 3.4+ |
安装完成后,记得在PyCharm中更新Python解释器,确认pytest已成功安装。这里有个明显的对比,未安装pytest时,运行测试用例需要手动编写main函数调用;安装后,测试方法名前会出现直接运行标志,无需额外代码,效率大幅提升。

核心基础:pytest用例运行规则
安装完成后,并不是所有方法都能被pytest识别并运行,必须遵循以下3条核心规则(重点记!):
-
文件名必须以test_开头或者_test结尾(如test_login.py、login_test.py);
-
测试类必须以Test开头,并且不能有__init__方法;
-
测试方法必须以test开头(如test_login_success、test_get_user_info)。
这里有个常见坑:测试类中不能定义__init__方法。因为pytest采用自动发现机制收集测试用例,会自动实例化测试类,若定义__init__方法,会掩盖测试逻辑,引入副作用,影响测试结果准确性。
如果测试类需要初始化操作,无需使用__init__,可以选择setup_method/teardown_method、setup_class/teardown_class方法,或后续会讲到的fixture函数,这些都是更合适的替代方案。
示例(错误示范,含__init__方法):
python
class Test():
def __init__(self):
print("-----init-------")
def test_a(self):
print("-----test_a----")

运行该代码会出现异常,无法正常执行测试用例,大家一定要避开这个错误。
命令行参数与配置文件
pytest提供了丰富的命令行参数,能灵活控制测试用例的执行,以下是新手最常用的几个参数,建议牢记:
| 命令 | 描述 | 备注 |
|---|---|---|
| pytest | 在当前目录及其子目录中搜索并运行测试 | 无 |
| pytest -v | 增加输出的详细程度 | 无 |
| pytest -s | 显示测试中的print语句 | 无 |
| pytest 文件名.py | 运行指定的测试模块 | 如pytest test_login.py |
| pytest -k 关键字 | 只运行测试名包含指定关键字的用例 | 如pytest -k login |
| pytest --html=report.html | 生成HTML格式的测试报告 | 需安装pytest-html插件 |
实操示例:
-
简单运行所有符合规则的用例:pytest(不显示print内容);
-
详细输出并显示print内容:pytest -sv(-s和-v可连写);
-
指定文件运行:pytest cases/test_01.py;
-
指定具体用例运行:pytest cases/test_01.py::Test::test_a。
但如果每次运行都要输入长长的命令,会非常繁琐。解决方案是创建pytest配置文件pytest.ini,将常用参数统一配置,后续只需输入pytest命令即可运行。
配置文件示例(核心配置):
将该文件放在项目根目录下,运行pytest命令时,会自动读取配置,无需再手动输入参数,极大提升效率。
必学技能:前后置操作与断言
前后置操作(解决初始化问题)
测试过程中,经常需要在执行用例前做初始化(如打开浏览器、连接数据库),执行后做清理(如关闭浏览器、断开数据库),这就是前后置操作。pytest提供3种方式实现,其中fixture是最推荐的方式。
-
setup_method 和 teardown_method:针对每个测试方法,执行一次前置和后置(如每个用例前都登录);
-
setup_class 和 teardown_class:针对整个测试类,只执行一次前置和后置(如类开始前初始化数据库,结束后关闭);
-
fixture:灵活度最高,可实现全局、局部的前后置,还能实现数据共享(后续详细讲解)。
示例:
python
class Testcase02():
def setup_method(self):
print("setup_method")
def test01(self):
print("test01")
def test02(self):
print("test02")
def teardown_method(self):
print("teardown_method")
class Test():
def setup_class(self):
print("setup_class")
def teardown_class(self):
print("teardown_class")
def test01(self):
print("test01")
def test02(self):
print("test02")

运行结果会显示,在含setup_method 和 teardown_method方法的测试类中,每个测试方法执行前都会打印前置信息,执行后打印后置信息。
在含有setup_class 和 teardown_class方法的测试类中,针对整个测试类,只执行一次前置和后置。
断言:验证测试结果的核心
断言是自动化测试的核心,用于验证实际结果是否符合预期。pytest无需额外导入断言库,直接使用Python原生的assert语句即可,语法简洁,功能强大。
基本语法:
assert 条件, 错误信息(错误信息可选,断言失败时显示)
条件必须是⼀个布尔表达式
常见断言场景示例:
python
import requests
import pytest
def test():
#断言基本数据类型
a = 1
b = 1
assert a== 1
str1 = "kiku"
str2 = "kiki"
assert str1 == str2
def test_ds():
# 断⾔列表
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_dict == actual_dict
# 断⾔集合
expect_set = {1, 2, 3, 'apple'}
actual_set = {1, 2, 3, 'apple'}
assert expect_set == actual_set
#测试接口返回数据所有内容:字段&字段值
def test2():
url = "https://jsonplaceholder.typicode.com/posts/1"
r = requests.get(url = url)
expect_data = {
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
actual_data = r.json()
assert expect_data == actual_data
#对关键字段进行校验
def test3():
url = "https://jsonplaceholder.typicode.com/comments?postId=1"
r = requests.get(url = url)
assert r.json()[0]['id'] == 1
def test4():
url = "https://jsonplaceholder.typicode.com/"
text = "Use your own data"
r = requests.get(url)
assert text in r.text
上面代码中用到的网站**http://jsonplaceholder.typicode.com/**是一个免费、公开、开箱即用的在线模拟 REST API 服务,专门用来给开发者做测试、学习、演示、原型开发。
它能提供现成的假接口 + 假数据,让我们在没有真实后端的情况下,也能练接口、写代码、做自动化测试。
断言失败时,pytest会清晰显示失败原因,方便我们快速定位问题,这也是pytest的一大优势。

pytest参数化设计**@pytest.mark.parametrize**
在测试中,经常需要用不同的参数重复运行同一个测试用例(如测试登录功能,输入不同的账号密码),如果手动编写多个用例,会造成代码冗余。
pytest内置的**@pytest.mark.parametrize**装饰器,可轻松实现参数化,大幅提升测试效率。
常见使用场景:
场景1:用例上使用参数化
python
#对多组参数实现参数化
@pytest.mark.parametrize("input, expected",[("3+5", 8), ("16/4", 4), ("4*9",37)])
def test7(input, expected):
assert eval(input) == expected

该用例会自动运行3次,分别使用3组参数,无需编写3个独立用例。
场景2:类上使用参数化(所有方法共用参数)
python
#在类上实现参数化
@pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
class TestClass:
def test_simple_case(self, n, expected):
assert n + 1 == expected
def test_weird_simple_case(self, n, expected):
assert (n * 1) + 1 == expected

要对模块中的所有测试进行参数化,还可以将 pytestmark 全局变量赋值:
python
#全局变量参数化
pytestmark = pytest.mark.parametrize("data",(1,2))
class TestA():
def testA1(self, data):
print(data)
def testA2(self, data):
print(data)
class TestB():
def testB1(self, data):
print(data)
def testB2(self, data):
print(data)
还可以自定义参数化数据源
python
def data_provider():
#...
return ["a","b",100]
@pytest.mark.parametrize("data", data_provider())()
def test_data(data):
print(data)
结合fixture还能实现更复杂的参数化,满足不同测试场景需求。
pytest核心:fixture的使用(重中之重)
fixture是pytest最强大的功能,它可以实现前后置操作、数据共享、资源管理等,比setup/teardown系列方法更灵活、更强大。
7.1 基本使用
使用@pytest.fixture装饰器标记一个函数,该函数就是fixture,测试用例只需将fixture函数名作为参数,即可调用fixture。
python
#使用fixture来调用方法
@pytest.fixture
def login():
print("登录login()")
def test_blogList(login):
print("博客列表页blogList()")
def test_blogDetail(login):
print("博客详情页blogDetail()")

运行结果会显示,两个测试用例执行前都会先执行login fixture的前置操作,实现了代码复用。
7.2 嵌套使用
fixture也能嵌套使用:
python
#fixture的嵌套
@pytest.fixture
def first():
return "a"
@pytest.fixture
def second(first):
return [first]
def test_string(second):
second.append("b")
assert second == ["a", "b"]
7.3请求多个fixture
测试和 fixture 不仅限于⼀次请求单个 fixture ,它们可以请求任意多个。
python
#请求多个fixtrue
class Fruit:
def __init__(self, name, color):
self.name = name
self.color = color
def __eq__(self, other):
return self.name == other.name
@pytest.fixture
def my_fruit():
return Fruit("apple", "red")
@pytest.fixture
def all_fruits(my_fruit):
return [Fruit("apple", "green") , Fruit("banana","yellow")]
def test_fruit(my_fruit, all_fruits):
assert my_fruit in all_fruits

7.4核心特性:yield fixture(前置+后置)
使用yield关键字,可在fixture中同时实现前置和后置操作:yield之前的代码是前置,yield之后的代码是后置,测试用例执行完成后,会自动执行后置代码,实现资源清理。
python
#yield fixtrue
@pytest.fixture
def operator():
print("前置操作:数据的初始化")
yield 100
print("后置操作:数据的清理")
def test_01(operator):
assert operator == 100
print(100+operator)
def test_02(operator):
print(100-operator)

常用场景:打开/关闭文件、连接/断开数据库等,避免资源泄露。
"Yield" fixture 使用 yield 而不是 return 。有了这些 fixture ,我们可以运行一些代码,并将对象返回给请求的 fixture/test ,就像其他 fixture ⼀样。唯⼀的不同是:
• return 被替换为 yield 。
• 该 fixture 的任何拆卸代码放置在 yield 之后。
⼀旦 pytest 确定了 fixture 的线性顺序,它将运行每个 fixture 直到它返回或 yield ,然后
继续执行列表中的下⼀个 fixture 做同样的事情。
测试完成后, pytest 将逆向遍历 fixture 列表,对于每个 yield 的 fixture ,运行yield
语句之后的代码
7.5 fixture参数(灵活控制作用范围)
fixture可通过参数控制作用范围、自动调用等,核心参数如下:
pytest.fixture(scope='', params='', autouse='', ids='', name='')
-
scope:作用范围(默认function,表示每个用例调用一次;class:每个类调用一次;module:每个模块(文件)调用一次;session:整个测试会话调用一次);
-
autouse:是否自动调用(默认False,需手动传入;True:所有用例自动调用,无需传入);
-
params:参数化fixture,实现多组参数测试;
-
name:给fixture重命名,调用时需使用新名称。
示例scope,范围function和class:
python
import pytest
@pytest.fixture(scope="function")
def fixture_01():
print("初始化")
yield
print("清理")
@pytest.fixture(scope="class")
def fixture_02():
print("初始化")
yield
print("清理")
class Testcase:
def test_01(self,fixture_01):
print("第1个测试用例")
def test_02(self,fixture_01):
print("第2个测试用例")
def test_03(self,fixture_02):
print("第1个测试用例")
def test_04(self,fixture_02):
print("第2个测试用例")

可以看到test_01和 test_02方法都各自执行了前置和后置操作,因为他们fixture的范围scope是function,
而 test_03和 test_04在一个类中 并且fixture范围scope是class,所以fixture只会在第一个方法开始前执行一次,在最后一个方法结束后执行一次。
运行后会发现,无需手动传入fixture,测试类的所有用例都会自动执行前置和后置操作。
另外两个scope范围在7.6中展示。
通过 params 实现参数化示例:
python
@pytest.mark.parametrize("data", (1,2,"kiku"))
def test_provider1(data):
print(data)
@pytest.fixture(params=[1,2,"kiku"])
def provider2(request):
return request.param
def test_provider2(provider2):
print(provider2)

我们可以看到通关fixture的params实现参数化的效果和用@pytest.mark.parametrize实现的效果相同。
7.6 全局fixture:conftest.py
如果多个测试模块需要共用fixture,无需重复编写,可创建conftest.py 文件(名称固定,不能修改),将全局fixture放在该文件中,所有子目录的测试用例均可直接调用,无需导入。
可以在项目中的不同目录下创建多个 conftest.py 文件,每个 conftest.py 文件都会对其所在目录及其子目录下的测试模块生效。
在不同模块的测试中需要用到 conftest.py 的前后置功能时,不需要做任何的import导入操作
作用:可以在不同的 .py 文件中使用同⼀个 fixture 函数。
conftest.py文件,范围为module,autouse为True 用例自动调用,不用手动传入
python
import pytest
@pytest.fixture(scope="module",autouse=True)
def fixture_01():
print("初始化")
yield
print("清理")
文件1:
python
def test_01(fixture_01):
print("第1个测试用例")
class Testcase2:
def test_02(self,fixture_01):
print("第2个测试用例")
文件2:
python
def test_01():
print("第1个测试用例")
class Testcase1:
def test_02(self):
print("第2个测试用例")
测试结果:

我们看到scope为moudle时,每个课执行的测试文件都调用了一次fixture前置操作和后置操作。
conftest.py文件,范围为session
python
import pytest
@pytest.fixture(scope="session", autouse=True)
def fixture_01():
print("初始化")
yield
print("清理")
测试文件内容不变,还是上面那两个,执行测试:

我们可以看到整个测试会话中fixture前置操作、后置操作都只执行了一次。
所有 .py 文件都在一个会话里,顺序按文件名跑,前后置只在最开始和最后各跑一次。

