目录
一、Pytest 是什么?
二、为什么选择 Pytest?
简洁性:化繁为简的优雅
灵活性:适应多变的测试需求
强大的插件生态:拓展无限可能
三、环境搭建
(一)安装 Python
(二)安装 Pytest
四、Pytest 基础语法
(一)测试用例的编写规则
(二)断言的使用
(三)简单示例演示
五、Pytest 进阶用法
(一)fixture 的使用
(二)参数化测试
(三)测试用例的执行控制
1. 命令行参数控制
2. 配置文件控制
六、Pytest 插件扩展
(一)pytest - cov:生成测试覆盖率报告
(二)pytest - html:生成 HTML 测试报告
(三)pytest - xdist:实现多进程并发测试
(四)pytest - rerunfailures:失败用例重试
七、实际项目应用案例
(一)结合项目架构组织测试用例
(二)与持续集成工具集成
八、总结与展望
一、Pytest 是什么?

在 Python 的测试领域中,Pytest 就如同一位全能型选手,占据着举足轻重的地位。它是一个成熟的全功能 Python 测试工具 ,凭借简洁、强大和高度可定制的特性,深受广大开发者和测试人员的喜爱。
Pytest 的诞生,为 Python 开发者提供了一个高效、灵活的测试解决方案,无论是简单的单元测试,还是复杂的功能测试,甚至是集成测试,Pytest 都能轻松应对。与 Python 自带的 unittest 测试框架相比,Pytest 更加简洁、直观,让测试代码的编写和维护变得更加容易。例如,使用 unittest 框架编写测试用例时,需要定义测试类并继承 unittest.TestCase,而在 Pytest 中,只需编写以test_开头的函数即可,大大减少了样板代码。
Pytest 支持丰富的测试场景,如参数化测试,通过@pytest.mark.parametrize装饰器,可以为同一个测试函数提供不同的参数组合,一次编写,多次测试,提高测试效率。同时,它还支持跳过某些测试用例、标记预期失败的测试用例等功能,让测试更加灵活可控。此外,Pytest 拥有强大的插件生态系统,通过安装各种插件,如pytest - html生成美观的 HTML 测试报告,pytest - cov计算代码覆盖率,pytest - xdist实现多进程并发测试等,可以满足不同项目的个性化测试需求。
二、为什么选择 Pytest?
在 Python 测试领域,测试框架百花齐放,那为何 Pytest 能脱颖而出,成为众多开发者的心头好呢?下面我们将 Pytest 与其他常见测试框架(如 unittest)进行对比,深入剖析 Pytest 的独特优势。
简洁性:化繁为简的优雅
unittest 作为 Python 标准库自带的测试框架,虽然功能全面,但用例格式相对复杂。使用 unittest 编写测试用例时,需要定义测试类并继承unittest.TestCase ,每个测试方法都需要以test_开头,且断言方法较为繁琐,如assertEqual、assertTrue等,记忆成本较高。例如:
import unittest
class TestMath(unittest.TestCase):
def test_add(self):
result = 2 + 3
self.assertEqual(result, 5)
if __name__ == '__main__':
unittest.main()
而 Pytest 则简洁得多,测试函数只需以test_开头,直接使用 Python 原生的assert语句进行断言,代码量大幅减少,更符合 Python “简洁即美” 的理念。同样的功能,用 Pytest 实现如下:
def test_add():
assert 2 + 3 == 5
运行 Pytest 时,只需在命令行输入pytest ,即可自动发现并执行测试用例,无需额外的运行配置代码,让测试代码的编写和执行都变得轻松愉快。
灵活性:适应多变的测试需求
Pytest 的灵活性体现在多个方面。在测试夹具(fixture)方面,它提供了比 unittest 更强大、更灵活的机制。unittest 主要通过setup、teardown等方法实现测试用例的前置和后置操作,作用域较为有限。而 Pytest 的 fixture 可以定义在函数、类、模块甚至会话级别,通过@pytest.fixture装饰器进行定义,并且可以方便地在不同测试用例之间共享和复用。例如,在测试数据库相关功能时,我们可以定义一个数据库连接的 fixture:
import pytest
import pymysql
@pytest.fixture(scope='module')
def db_connection():
connection = pymysql.connect(host='localhost', user='root', password='password', database='test_db')
yield connection
connection.close()
def test_query(db_connection):
cursor = db_connection.cursor()
cursor.execute('SELECT * FROM users')
result = cursor.fetchall()
assert len(result) > 0
在这个例子中,db_connection fixture 的作用域为模块级别,这意味着在整个模块的测试用例执行期间,只创建一次数据库连接,大大提高了测试效率。而且,yield语句之前的代码在测试用例执行前执行,yield语句之后的代码在测试用例执行后执行,用于清理资源,这种设计非常灵活。
此外,Pytest 还支持丰富的测试标记(mark)功能。通过@pytest.mark装饰器,可以为测试用例添加自定义标记,然后在运行测试时根据标记来筛选执行特定的测试用例。比如,我们可以将一些耗时较长的测试用例标记为slow ,在日常开发测试时跳过这些用例,只在全面测试时执行,提高测试效率。
@pytest.mark.slow
def test_slow_operation():
# 模拟耗时操作
import time
time.sleep(5)
assert True
运行时,使用pytest -m "not slow"命令即可跳过标记为slow的测试用例。
强大的插件生态:拓展无限可能
Pytest 拥有一个庞大且活跃的插件生态系统,这是它的一大显著优势。截至目前,已有上千个各式各样的插件可供使用,这些插件可以轻松扩展 Pytest 的功能,满足各种不同的测试需求。
在测试报告方面,pytest - html插件可以生成美观、详细的 HTML 格式测试报告,报告中不仅包含测试用例的执行结果,还能展示测试用例的执行时间、断言信息等,方便测试人员和开发人员查看和分析测试结果。allure - pytest插件则能生成更加专业、丰富的 Allure 报告,支持多种展示形式和交互功能,如趋势图、错误分析等,在团队协作和项目汇报中非常实用。
在测试效率提升方面,pytest - xdist插件允许并行运行测试用例,充分利用多核 CPU 的优势,大幅缩短测试执行时间,尤其适用于大型项目的测试。pytest - rerunfailures插件可以在测试用例失败时自动重试,对于一些由于环境不稳定或偶尔出现的问题导致的测试失败,非常有用,减少了人为排查和重新运行测试的工作量。
在与其他框架和工具集成方面,Pytest 也表现出色。例如,pytest - django插件专门为 Django 项目提供支持,使得在 Django 项目中使用 Pytest 进行测试变得更加便捷;pytest - selenium插件则将 Pytest 与 Selenium 相结合,方便进行 Web 自动化测试 。
三、环境搭建
在开始使用 Pytest 编写测试用例之前,我们需要先搭建好开发环境,主要包括安装 Python 和 Pytest。下面将详细介绍这两个关键步骤。
(一)安装 Python
Python 是 Pytest 运行的基础,因此首先要确保系统中安装了 Python。Python 的下载渠道丰富多样,以下为你推荐几个常用且可靠的下载方式:
- Python 官网:这是获取 Python 安装包最直接、最官方的途径。打开浏览器,访问Python 官方网站 ,在首页中,你会看到醒目的 “Download” 按钮,点击它进入下载页面。在这里,你可以选择适合自己操作系统(Windows、macOS、Linux 等)和系统位数(32 位或 64 位)的 Python 版本。通常情况下,建议选择最新的稳定版本,以获取最新的功能和性能优化。例如,目前最新的 Python 3.12 版本在性能和安全性上都有显著提升。下载完成后,运行安装程序,按照安装向导的提示进行操作。在安装过程中,务必勾选 “Add Python to PATH” 选项,这样可以将 Python 添加到系统环境变量中,方便后续在命令行中直接使用 Python 命令。
- Anaconda:如果你主要从事数据科学、机器学习等领域的开发,Anaconda 是一个不错的选择。Anaconda 是一个 Python 的发行版本,它不仅包含了 Python,还集成了大量常用的科学计算库和工具,如 NumPy、Pandas、Matplotlib 等。你可以访问Anaconda 官方网站 下载适合自己系统的安装包。安装过程中,同样建议勾选将 Anaconda 添加到系统 PATH 环境变量的选项。安装完成后,Anaconda 会自带一个包管理器 conda,通过 conda 可以方便地创建虚拟环境、安装和管理 Python 包。比如,使用conda create --name myenv python=3.8命令可以创建一个名为myenv,Python 版本为 3.8 的虚拟环境;使用conda install numpy命令可以在当前激活的虚拟环境中安装 NumPy 库。
- Microsoft Store(仅适用于 Windows 用户):对于 Windows 10 和 Windows 11 用户,Microsoft Store 提供了一种便捷的 Python 安装方式。打开 Microsoft Store,在搜索框中输入 “Python”,然后选择适合的 Python 版本,点击 “获取” 按钮即可开始安装。安装完成后,可直接在开始菜单中找到 Python 的相关启动项。这种方式安装的 Python 版本也是官方版本,更新较为及时,并且安装过程无需手动配置环境变量,非常适合新手用户。
(二)安装 Pytest
在安装好 Python 之后,就可以通过 pip 命令来安装 Pytest 了。pip 是 Python 的包管理工具,它可以方便地安装、升级和卸载 Python 包。安装 Pytest 的步骤如下:
- 打开命令行终端(在 Windows 系统中,可以通过 “开始菜单” 搜索 “命令提示符” 或 “PowerShell”;在 macOS 和 Linux 系统中,直接打开终端应用)。
- 在命令行中输入以下命令:
pip install pytest
如果你的系统中同时安装了 Python 2 和 Python 3,并且希望使用 Python 3 的 pip 来安装 Pytest,可以使用pip3命令,即pip3 install pytest 。
3. 等待安装完成。在安装过程中,pip 会自动下载 Pytest 及其依赖项,并将它们安装到 Python 的环境中。安装完成后,你会看到类似 “Successfully installed pytest-7.4.3” 的提示信息,表示 Pytest 已成功安装。
在安装过程中,可能会遇到一些问题,以下是常见问题及解决方法:
- 网络问题:如果在安装过程中出现下载缓慢或下载失败的情况,可能是网络连接不稳定或 pip 源的问题。可以尝试更换 pip 源,使用国内的镜像源,如清华大学的镜像源。在命令行中执行以下命令来临时更换 pip 源:
pip install pytest -i https://pypi.tuna.tsinghua.edu.cn/simple
如果希望永久更换 pip 源,可以在用户主目录下创建一个pip文件夹(如果不存在),在该文件夹中创建一个pip.conf文件(Windows 系统中为pip.ini),然后在文件中添加以下内容:
[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
- 权限问题:在某些系统中,可能会因为权限不足而导致安装失败。如果遇到权限错误提示,可以尝试使用管理员权限运行命令行终端。在 Windows 系统中,右键点击 “命令提示符” 或 “PowerShell”,选择 “以管理员身份运行”;在 macOS 和 Linux 系统中,在命令前加上sudo,如sudo pip install pytest 。不过,使用sudo命令需要谨慎,因为它会以超级用户权限执行命令,可能会对系统造成潜在风险。
- 版本冲突:如果系统中已经安装了旧版本的 Pytest,在安装新版本时可能会出现版本冲突问题。可以先使用pip uninstall pytest命令卸载旧版本,然后再重新安装新版本。此外,还可以使用pip install --upgrade pytest命令来升级已安装的 Pytest 到最新版本。
四、Pytest 基础语法
(一)测试用例的编写规则
在 Pytest 中,测试用例的编写遵循一定的命名规则,这有助于 Pytest 自动发现和执行测试。
- 测试文件命名:测试文件应命名为以test_开头或_test结尾的 Python 文件,例如test_math_operations.py或math_operations_test.py 。这种命名方式让 Pytest 在执行测试时能够轻松识别测试文件,将其纳入测试范围。
- 测试函数命名:测试函数必须以test_开头,例如test_add()、test_subtract()等。这样 Pytest 在扫描测试文件时,能够准确找到每个测试函数并执行相应的测试逻辑。例如:
def test_add():
result = 2 + 3
assert result == 5
- 测试类命名:如果使用测试类来组织测试用例,测试类必须以Test开头,且不能包含__init__方法,类中的测试方法同样要以test_开头 。例如:
class TestMath:
def test_subtract(self):
result = 5 - 3
assert result == 2
此外,Pytest 还支持使用装饰器来标记测试用例,为测试用例添加额外的元数据或执行条件。例如,@pytest.mark.smoke可以将某个测试用例标记为冒烟测试用例,在执行测试时,可以通过-m参数指定只运行冒烟测试用例:
import pytest
@pytest.mark.smoke
def test_function():
assert True
运行时使用pytest -m smoke命令即可。
(二)断言的使用
断言是测试用例的核心,用于验证测试结果是否符合预期。Pytest 中使用 Python 原生的assert语句进行断言,简单直观,同时 Pytest 还对断言失败时的错误信息进行了优化,使其更易于调试。
- 基本断言:最常见的断言方式是判断两个值是否相等,使用assert a == b 。例如:
def test_equal():
num1 = 10
num2 = 10
assert num1 == num2
- 判断真假:使用assert xx判断表达式xx是否为真,assert not xx判断表达式xx是否为假。比如:
def test_true():
condition = True
assert condition
def test_false():
condition = False
assert not condition
- 包含关系断言:使用assert a in b判断b是否包含a 。例如,判断一个列表中是否包含某个元素:
def test_in():
my_list = [1, 2, 3, 4, 5]
assert 3 in my_list
- 异常断言:在某些情况下,我们需要验证函数是否会抛出特定的异常,这时可以使用with pytest.raises()上下文管理器。例如,验证一个除法函数在除数为零时是否抛出ZeroDivisionError异常:
def divide(a, b):
return a / b
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError):
divide(10, 0)
- 断言浮点数:由于浮点数在计算机中的存储存在精度问题,直接使用==判断两个浮点数是否相等可能会得到错误的结果。Pytest 提供了pytest.approx方法来断言两个浮点数是否在一定的误差范围内相等。例如:
def test_float():
result = 0.1 + 0.2
assert result == pytest.approx(0.3)
(三)简单示例演示
下面通过一个简单的加法函数测试,展示完整的测试用例编写过程。假设我们有一个math_operations.py文件,其中定义了一个加法函数add:
# math_operations.py
def add(a, b):
return a + b
然后,我们在test_math_operations.py文件中编写测试用例:
# test_math_operations.py
from math_operations import add
def test_add():
result = add(2, 3)
assert result == 5
def test_add_negative_numbers():
result = add(-1, 1)
assert result == 0
def test_add_zero():
result = add(0, 0)
assert result == 0
在这个示例中,我们定义了三个测试用例,分别测试正常的加法运算、负数相加以及加零的情况。每个测试用例都使用assert语句来验证函数的返回值是否符合预期。运行pytest命令,Pytest 会自动发现并执行这些测试用例,并输出详细的测试结果,帮助我们快速了解测试的通过情况和可能存在的问题。
五、Pytest 进阶用法
(一)fixture 的使用
在 Pytest 中,fixture 是一个强大的功能,它用于在测试执行前进行准备工作,并在测试执行后进行清理工作,极大地提高了测试代码的复用性和可维护性。fixture 可以被多个测试用例共享,就像是为测试用例提供了一个定制化的 “测试环境” 。
例如,在测试一个需要数据库连接的函数时,我们可以使用 fixture 来创建和管理数据库连接。假设我们有一个简单的数据库查询函数query_database ,它从数据库中获取用户信息:
import pymysql
def query_database():
connection = pymysql.connect(host='localhost', user='root', password='password', database='test_db')
cursor = connection.cursor()
cursor.execute('SELECT * FROM users')
result = cursor.fetchall()
connection.close()
return result
现在,我们使用 Pytest 的 fixture 来管理数据库连接,在测试前创建连接,测试后关闭连接。
import pytest
import pymysql
@pytest.fixture
def db_connection():
connection = pymysql.connect(host='localhost', user='root', password='password', database='test_db')
yield connection
connection.close()
def test_query_database(db_connection):
cursor = db_connection.cursor()
cursor.execute('SELECT * FROM users')
result = cursor.fetchall()
assert len(result) > 0
在这个例子中,db_connection是一个 fixture 函数,通过@pytest.fixture装饰器进行定义。在test_query_database测试函数中,将db_connection作为参数传入,Pytest 会自动调用db_connection fixture 函数,并将其返回值(即数据库连接对象)传递给测试函数。yield语句之前的代码(创建数据库连接)会在测试函数执行前执行,yield语句之后的代码(关闭数据库连接)会在测试函数执行后执行,确保了数据库连接的正确管理,避免了在每个测试函数中重复编写连接和关闭数据库的代码 。
fixture 还支持不同的作用域(scope),通过scope参数进行设置,常见的作用域有:
- function:默认作用域,每个测试函数都会调用一次 fixture,在测试函数执行前创建资源,执行后清理资源。
- class:每个测试类调用一次 fixture,在测试类的所有测试方法执行前创建资源,所有测试方法执行后清理资源。例如:
import pytest
@pytest.fixture(scope='class')
def class_fixture():
data = [1, 2, 3]
yield data
# 清理代码,这里省略
class TestClass:
def test_method1(self, class_fixture):
assert sum(class_fixture) == 6
在这个例子中,class_fixture在TestClass类的所有测试方法中只创建一次,提高了测试效率。
- module:每个测试模块调用一次 fixture,在模块的第一个测试开始前创建资源,模块中的所有测试完成后清理资源。常用于模块级别的资源管理,如建立和断开与外部服务的连接。
- session:整个测试会话中只调用一次 fixture,在测试会话开始前创建资源,测试会话结束后清理资源。适用于开销较大,且所有测试用例都可以共享的准备步骤,如复杂的系统级设置或一次性的资源分配 。
(二)参数化测试
参数化测试是 Pytest 的另一个强大功能,通过@pytest.mark.parametrize装饰器,我们可以使用不同的参数组合来运行同一个测试用例,从而提高测试覆盖率和效率。
例如,我们有一个简单的加法函数add :
def add(a, b):
return a + b
为了测试这个函数在不同输入情况下的正确性,我们可以使用参数化测试:
import pytest
def add(a, b):
return a + b
@pytest.mark.parametrize("a, b, expected", [
(2, 3, 5),
(-1, 1, 0),
(0, 0, 0)
])
def test_add(a, b, expected):
result = add(a, b)
assert result == expected
在这个例子中,@pytest.mark.parametrize装饰器接受两个参数:第一个参数是一个字符串,包含了测试函数中的参数名,多个参数名用逗号隔开;第二个参数是一个列表,列表中的每个元素都是一个元组,每个元组对应一组参数值。Pytest 会根据参数值列表,自动运行测试函数多次,每次使用一组参数值进行测试 。
参数化测试还支持更复杂的参数组合和动态生成参数。比如,我们可以使用嵌套的@pytest.mark.parametrize装饰器来测试两个字符串连接后的结果:
import pytest
def concat(s1, s2):
return s1 + s2
@pytest.mark.parametrize("s1", ["hello", "world"])
@pytest.mark.parametrize("s2", ["python", "pytest"])
def test_concat(s1, s2):
result = concat(s1, s2)
assert result == s1 + s2
在这个例子中,s1有两个取值,s2也有两个取值,通过嵌套的参数化装饰器,Pytest 会自动生成 4 种参数组合,运行测试函数 4 次,全面地测试了concat函数在不同字符串组合下的正确性 。
(三)测试用例的执行控制
在实际测试过程中,我们常常需要灵活控制测试用例的执行,Pytest 提供了丰富的方式来满足这一需求,通过命令行参数和配置文件,我们可以轻松实现对测试用例执行顺序、选择执行部分测试用例等操作 。
1. 命令行参数控制
- 指定执行部分测试用例:
- 按模块执行:使用pytest test_module.py命令可以只执行test_module.py模块中的测试用例。例如,项目中有test_math.py和test_db.py两个测试模块,如果只想执行test_math.py中的测试用例,在命令行中输入pytest test_math.py即可。
- 按类执行:pytest test_module.py::TestClass可以执行test_module.py模块中TestClass类的所有测试用例。假设test_math.py中有一个TestMathOperations类,要执行该类的测试用例,命令为pytest test_math.py::TestMathOperations 。
- 按函数执行:pytest test_module.py::TestClass::test_method可以精确到执行test_module.py模块中TestClass类里的test_method测试函数。如执行test_math.py中TestMathOperations类的test_add方法,命令是pytest test_math.py::TestMathOperations::test_add 。
- 按关键字匹配执行:使用-k参数,后面跟关键字,Pytest 会执行包含该关键字的测试用例。比如pytest -k "add and test"会执行所有包含 “add” 和 “test” 关键字的测试用例,对于快速筛选特定功能的测试用例非常方便。还可以使用逻辑运算符,如pytest -k "not slow"表示不执行包含 “slow” 关键字的测试用例,常用于跳过一些耗时较长的测试 。
- 控制测试用例执行顺序:可以使用pytest -ordering插件来控制测试用例的执行顺序。安装插件后,通过@pytest.mark.run(order=X)装饰器来指定测试用例的执行顺序,X为数字,数字越小越先执行。例如:
import pytest
@pytest.mark.run(order=1)
def test_first():
assert True
@pytest.mark.run(order=2)
def test_second():
assert True
在这个例子中,test_first会先于test_second执行。
2. 配置文件控制
Pytest 支持通过配置文件pytest.ini来改变默认的测试行为。pytest.ini一般放在项目的根目录,编码格式必须是 ANSI 格式(可以使用 notepad++ 等工具修改编码格式) 。以下是一些常见的配置项:
- 设置命令行参数:使用addopts配置项可以设置默认的命令行参数。例如:
[pytest]
addopts = -vs
这表示每次运行pytest命令时,都会默认带上-v(详细输出)和-s(显示测试函数中的打印信息)参数。
- 指定测试用例路径:通过testpaths配置项指定测试用例所在的目录。比如:
[pytest]
testpaths = tests
这样 Pytest 只会在tests目录及其子目录中查找测试用例,忽略其他目录的测试文件 。
- 指定测试文件、类和函数命名规则:
[pytest]
python_files = test_*.py
python_classes = Test*
python_functions = test_*
上述配置表示 Pytest 会查找文件名以test_开头的 Python 文件,类名以Test开头的测试类,以及函数名以test_开头的测试函数,方便统一管理和组织测试用例。通过合理配置pytest.ini文件,可以让 Pytest 的测试执行更加符合项目的需求和规范 。
六、Pytest 插件扩展
Pytest 之所以如此强大和受欢迎,很大程度上得益于其丰富的插件生态系统。通过插件,我们可以轻松扩展 Pytest 的功能,满足各种复杂的测试需求。下面为大家介绍一些常用的 Pytest 插件及其安装和使用方法。
(一)pytest - cov:生成测试覆盖率报告
在软件开发中,测试覆盖率是衡量测试质量的重要指标之一,它反映了代码中被测试覆盖的比例。pytest - cov插件可以帮助我们方便地生成测试覆盖率报告,让我们清楚地了解哪些代码被测试覆盖,哪些还没有,从而有针对性地改进测试用例。
- 安装插件:使用 pip 命令进行安装:
pip install pytest - cov
- 使用方法:假设我们有一个简单的 Python 项目,结构如下:
my_project/
│
├── my_module.py
└── tests/
└── test_my_module.py
在my_module.py中定义了一个函数:
# my_module.py
def add(a, b):
return a + b
在test_my_module.py中编写测试用例:
# test_my_module.py
from my_project.my_module import add
def test_add():
result = add(2, 3)
assert result == 5
运行测试并生成覆盖率报告,在命令行中执行:
pytest --cov=my_project
执行后,会在终端输出详细的覆盖率信息,类似如下:
---------- coverage: platform win32, python 3.10.8-final-0 -----------
Name Stmts Miss Cover
---------------------------------------
my_project\my_module.py 3 0 100%
---------------------------------------
TOTAL 3 0 100%
这表明my_module.py中的代码被测试完全覆盖。如果希望生成 HTML 格式的覆盖率报告,以便更直观地查看,可以使用如下命令:
pytest --cov=my_project --cov - report=html
执行后,会在当前目录下生成一个htmlcov文件夹,里面包含了 HTML 格式的覆盖率报告,使用浏览器打开htmlcov/index.html文件,即可看到详细的覆盖率报告,报告中会用不同颜色标识出被覆盖和未被覆盖的代码行,非常直观。
(二)pytest - html:生成 HTML 测试报告
Pytest 默认的测试结果输出相对简单,在实际项目中,我们常常需要一份更直观、详细的测试报告,方便向团队成员或客户展示测试结果。pytest - html插件可以将测试结果生成美观的 HTML 报告,报告中包含测试用例的执行结果、执行时间、断言信息等详细内容。
- 安装插件:使用 pip 安装:
pip install pytest - html
- 使用方法:还是以上述项目为例,运行测试并生成 HTML 报告,在命令行中执行:
pytest --html=report.html
执行后,会在当前目录下生成一个report.html文件,用浏览器打开该文件,即可看到生成的 HTML 测试报告。报告的头部会显示测试的基本信息,如测试时间、测试环境等;中间部分是测试用例的详细执行情况,包括每个测试用例的名称、状态(通过、失败、跳过等)、执行时间等;底部还会有测试结果的汇总信息,如通过的用例数、失败的用例数、跳过的用例数等。如果测试用例失败,报告中还会详细显示失败的原因和堆栈跟踪信息,方便我们定位问题。此外,还可以通过--self - contained - html参数生成自包含的 HTML 报告,即报告中包含了所有的样式和资源,方便分享和查看,命令如下:
pytest --html=report.html --self - contained - html
(三)pytest - xdist:实现多进程并发测试
在测试用例较多时,测试执行时间可能会很长,这在一定程度上影响了开发效率。pytest - xdist插件允许我们将测试用例分发到多个 CPU 核心或远程主机上并行执行,充分利用多核 CPU 的优势,大大缩短测试执行时间。
- 安装插件:使用 pip 安装:
pip install pytest - xdist
- 使用方法:假设我们有多个测试用例,例如:
# test_example.py
import pytest
import time
def test_case1():
time.sleep(2)
assert 1 + 1 == 2
def test_case2():
time.sleep(2)
assert 2 + 2 == 4
def test_case3():
time.sleep(2)
assert 3 + 3 == 6
在命令行中使用-n参数指定并行执行的进程数,例如使用 4 个进程并行执行测试:
pytest -n 4 test_example.py
或者使用-n auto参数让插件自动检测 CPU 核心数并使用所有核心:
pytest -n auto test_example.py
通过这种方式,原本按顺序执行需要较长时间的测试用例,在并行执行的情况下可以大幅缩短执行时间,提高测试效率。需要注意的是,使用多进程并发测试时,测试用例之间应尽量保持独立性,避免共享资源带来的并发问题 。
(四)pytest - rerunfailures:失败用例重试
在测试过程中,有时会遇到一些非确定性的错误,例如由于网络波动、环境不稳定等原因导致测试用例失败,但再次运行时可能又能通过。pytest - rerunfailures插件可以帮助我们在测试用例失败时自动重试,减少因这类偶发性问题导致的测试误报。
- 安装插件:使用 pip 安装:
pip install pytest - rerunfailures
- 使用方法:在命令行中使用--reruns参数指定失败后重试的次数,例如重试 3 次:
pytest --reruns 3 test_example.py
如果希望在每次重试之间添加延迟,可以使用--reruns - delay参数指定延迟时间(单位为秒),例如重试 3 次,每次重试间隔 1 秒:
pytest --reruns 3 --reruns - delay 1 test_example.py
此外,也可以在测试函数上使用@pytest.mark.flaky(reruns = 次数, reruns_delay = 延迟时间)装饰器来单独指定某个测试用例的重试策略,例如:
import pytest
@pytest.mark.flaky(reruns = 2, reruns_delay = 0.5)
def test_flaky():
import random
assert random.randint(1, 10) != 5
在这个例子中,test_flaky测试用例如果失败,会自动重试 2 次,每次重试间隔 0.5 秒 。
七、实际项目应用案例
(一)结合项目架构组织测试用例
在一个基于 Django 框架的 Web 项目中,我们使用 Pytest 进行测试用例的编写和管理。项目结构如下:
my_django_project/
│
├── my_django_project/
│ ├── settings.py
│ ├── urls.py
│ └──...
│
├── apps/
│ ├── users/
│ │ ├── models.py
│ │ ├── views.py
│ │ └── tests/
│ │ ├── test_models.py
│ │ └── test_views.py
│ └── products/
│ ├── models.py
│ ├── views.py
│ └── tests/
│ ├── test_models.py
│ └── test_views.py
│
└── tests/
├── conftest.py
└── test_utils.py
根据项目架构,我们将测试用例按应用(app)进行分组,每个 app 的测试用例放在各自的tests目录下。例如,users app 的模型测试用例放在users/tests/test_models.py中,视图测试用例放在users/tests/test_views.py中 。
在test_models.py中,使用 fixture 来创建测试数据:
import pytest
from apps.users.models import User
@pytest.fixture
def user_data():
return {
'username': 'testuser',
'password': 'testpassword',
'email': 'test@example.com'
}
@pytest.fixture
def create_user(user_data):
user = User.objects.create_user(**user_data)
return user
def test_user_creation(create_user):
assert create_user.username == 'testuser'
在test_views.py中,测试视图函数时,使用pytest - django插件提供的client fixture 来模拟 HTTP 请求:
import pytest
from django.urls import reverse
def test_user_login(client, user_data):
url = reverse('users:login')
response = client.post(url, data=user_data)
assert response.status_code == 200
通过这种方式,将测试用例与项目架构紧密结合,使得测试代码的组织清晰、易于维护,并且方便定位和排查问题 。
(二)与持续集成工具集成
- 与 Jenkins 集成:Jenkins 是一款广泛使用的持续集成工具,将 Pytest 与 Jenkins 集成,可以实现代码提交后自动触发测试,并生成详细的测试报告。
- 安装插件:在 Jenkins 的插件管理中,搜索并安装 “Publish JUnit test result report” 插件(用于展示测试结果)和 “Allure Jenkins Plugin” 插件(用于生成 Allure 测试报告,如果需要)。
- 配置项目:新建一个 Jenkins 项目,在 “源码管理” 中配置项目的 Git 仓库地址和认证信息,以便 Jenkins 能够拉取最新代码。在 “构建” 步骤中,添加一个执行 Shell 脚本(如果是 Linux 系统)或 Windows 批处理命令(如果是 Windows 系统)的步骤,用于运行 Pytest 测试。例如:
#!/bin/bash
# 进入项目目录
cd /path/to/my_project
# 激活虚拟环境(如果使用虚拟环境)
source venv/bin/activate
# 运行Pytest测试并生成JUnit格式的测试报告
pytest --junitxml=test-results.xml
# 生成Allure报告(如果安装了Allure插件)
allure generate --clean allure-results -o allure-report
- 配置后操作:在 “构建后操作” 中,添加 “Publish JUnit test result report” 步骤,指定测试报告文件路径(test-results.xml),这样 Jenkins 会在构建完成后展示测试结果的汇总信息,包括通过、失败和跳过的测试用例数量等。如果使用 Allure 报告,添加 “Allure Report” 步骤,指定 Allure 报告的结果目录(allure-results)和报告输出目录(allure-report),构建完成后可以在 Jenkins 界面中点击查看 Allure 报告,报告中包含详细的测试用例执行信息、趋势图等,方便团队成员了解测试情况。
- 与 GitLab CI/CD 集成:GitLab CI/CD 是 GitLab 提供的持续集成和持续交付服务,与 GitLab 仓库无缝集成。在项目根目录下创建一个.gitlab-ci.yml文件,用于配置 CI/CD 流程 。
stages:
- test
test:
stage: test
image: python:3.10
script:
- pip install -r requirements.txt
- pytest --cov=my_project --cov-report=xml
- allure generate --clean allure-results -o allure-report
artifacts:
when: always
paths:
- allure-report
- coverage.xml
在这个配置中,定义了一个test阶段,使用python:3.10镜像,安装项目依赖后运行 Pytest 测试,并生成覆盖率报告和 Allure 报告。artifacts部分配置了在构建完成后保留 Allure 报告和覆盖率报告文件,方便在 GitLab 界面中查看。每次代码推送到 GitLab 仓库时,GitLab CI/CD 会自动触发构建和测试流程,开发者可以在 GitLab 的 CI/CD 页面中查看测试结果和报告,及时了解代码的质量情况,确保每次代码变更都经过充分的测试 。
八、总结与展望
通过本文的学习,相信大家对 Pytest 已经有了较为全面和深入的了解。从基础语法到进阶用法,再到插件扩展和实际项目应用,Pytest 展现出了其强大的功能和灵活性,无论是初学者还是有经验的开发者,都能在 Pytest 的世界中找到适合自己的应用场景。
在基础语法部分,我们掌握了测试用例的编写规则,包括测试文件、函数和类的命名规范,以及使用 Python 原生assert语句进行断言的方法,这些是编写 Pytest 测试用例的基石 。
进阶用法中的 fixture 让我们能够高效地管理测试资源,实现测试环境的复用;参数化测试则极大地提高了测试覆盖率,减少了重复代码;通过命令行参数和配置文件对测试用例执行的灵活控制,使我们能够根据不同的需求定制测试执行策略 。
Pytest 丰富的插件生态系统进一步拓展了其功能边界,pytest - cov帮助我们分析代码覆盖率,pytest - html生成直观的 HTML 测试报告,pytest - xdist实现多进程并发测试加速,pytest - rerunfailures处理失败用例的重试,这些插件在实际项目中都发挥着重要作用 。
在实际项目应用中,我们学会了结合项目架构组织测试用例,使其结构清晰、易于维护;还了解了如何将 Pytest 与 Jenkins、GitLab CI/CD 等持续集成工具集成,实现自动化测试流程,及时发现代码中的问题,保证软件质量 。
然而,Pytest 的功能远不止于此,它还有许多高级特性和应用场景等待我们去探索。例如,自定义插件以满足特定项目的需求,深入研究 fixture 的复杂依赖关系和动态生成,以及在分布式测试环境中的进一步优化等 。希望大家在今后的工作和学习中,能够不断实践和探索 Pytest 的更多功能,将其更好地应用到自动化测试中,提升测试效率和质量,为软件开发的质量保障贡献自己的力量 。
















