实用pytest教程

在这里插入图片描述

简介

Pytest 最初由 Holger Krekel 开发,第一个版本在2004年发布。它旨在提供一个简单、可扩展、非侵入式的方式来编写和执行测试。与 Python 标准库中的 unittest 模块和 nose 测试框架相比,Pytest 提供了一种更为简洁的方式来编写测试用例。

Pytest 使用的是平面测试模式,不强制要求测试用例必须是类的方法,函数式的写法使得测试代码看起来更为简练。此外,Pytest 通过内置的 assert 语句重写机制,允许开发者直接使用 Python 的 assert 关键字进行断言,而无需学习特定的库或框架的断言方法。

背景

在 Pytest 出现之前,Python 的测试框架选择不多。unittest(又叫 PyUnit)是 Python 标准库的一部分,它受到 Java 的 JUnit 测试框架的启发,采用了类似的面向对象的方式来组织测试。不过,unittest 的测试样板代码较多,不够灵活。

为了解决 unittest 的这些不足,诸如 nose 等新的框架被开发出来,提供了更灵活的测试发现机制和插件支持。而 Pytest 则进一步简化了测试代码的编写,使其更加自然和 Pythonic。

Pytest 逐渐获得了广泛的接受,并且形成了一个活跃的社区,开发了许多插件来扩展其核心功能。这些插件覆盖了从并行测试、测试覆盖率到集成具体 web 框架的各种用途。

随着时间的推移,Pytest 已经成为了 Python 领域推荐的测试框架之一,尤其是在开源项目和现代 Python 开发实践中。由于其易用性和强大的功能,Pytest 成为了许多知名项目(如 requests、Django)的测试工具选择。它同样适用于小型项目和大型、复杂的系统。Pytest 通常与持续集成(CI)流程结合使用,以自动化测试过程并确保软件质量。

核心特性

Pytest 是一个功能丰富的 Python 测试框架,它的核心特性使其在自动化测试领域非常受欢迎。以下是 pytest 的一些关键特性:

  1. 无需样板代码:与 unittest 不同,pytest 允许你编写不需要类和方法的测试函数。这减少了许多样板代码,使得测试更加简洁易读。

  2. 断言重写:Pytest 对 assert 语句进行了重写,可以提供详细的断言失败信息。这意味着你可以直接使用 Python 的 assert 语句而不是使用特定的断言方法。

  3. 强大的 fixture 系统:pytest 的 fixtures 允许你定义函数,这些函数可以用来为测试提供一个固定的基线环境(比如数据库连接、测试数据等)。这些 fixtures 可以被多个测试用例重用,并且 pytest 会处理其生命周期。

  4. 参数化测试:使用 @pytest.mark.parametrize 装饰器,你可以轻松地对一个测试用例应用多组参数。这极大地提高了测试的灵活性和覆盖范围。

  5. 自动发现测试:pytest 可以自动发现项目中的测试模块和函数,它们通常以 test_ 开头或者 _test 结尾。

  6. 丰富的插件生态系统:Pytest 拥有强大的插件生态系统,其中包括成百上千的插件,它们可以用来扩展 pytest 的核心功能。这包括生成 HTML 报告、测试覆盖率报告、与其他测试服务集成等。

  7. 易于集成:Pytest 可以很容易地与持续集成工具如 Jenkins、Travis CI 和 GitHub Actions 集成。这使得自动执行测试变得简单。

  8. 灵活的配置选项:通过 pytest.ini、conftest.py 文件或命令行选项,你可以灵活配置 pytest 的行为。

  9. 支持多种测试类型:你可以使用 pytest 来进行单元测试、集成测试、端到端测试和任何需要自动化的测试。

  10. 可扩展性:Pytest 允许你编写自定义插件来添加新的命令行选项、扩展其内部功能、添加钩子(hooks)来改变其执行流程等。

  11. 并行测试执行:通过 pytest-xdist 插件,pytest 可以并行执行测试,从而显著减少测试时间。

跳过和预期失败的测试:你可以使用 @pytest.mark.skip 和 @pytest.mark.xfail 对无法执行或预期会失败的测试进行标记。

这些特性使得 pytest 变得非常适用于各种规模的项目,从简单的小型项目到需要复杂测试策略的大型企业应用。它的易用性和灵活性也使得许多 Python 开发者将其作为首选的测试框架。

安装与设置

Pytest 是一个用 Python 编写的测试框架,它可以使用 pip 进行安装。以下是安装和设置 pytest 的基本步骤:

  1. 安装 pytest:
    首先确保你已经安装了 Python 和 pip。Pytest 支持多个版本的 Python,包括 Python 3.5 及以上版本。
    安装 pytest 通常只需要以下命令:
pip install pytest

如果你想要安装特定版本的 pytest,可以指定版本号:

pip install pytest==x.y.z

其中 x.y.z 是 pytest 的版本号。

  1. 创建测试文件:

创建一个新的 Python 文件来编写你的测试。按照 pytest 的约定,你的测试文件名应该以 test_ 开头或者以 _test 结尾。

例如:test_example.py

  1. 编写测试函数:

在测试文件中,编写以 test_ 开头的函数。这些函数将被 pytest 识别为测试用例。

# test_example.py

def test_example():
    assert 1 == 1  # 测试将会通过
  1. 运行测试:

打开命令行或终端,并切换到包含测试文件的目录。运行 pytest 命令将自动发现并执行测试:

pytest

pytest 会查找当前目录及其子目录中所有符合命名约定的测试文件,并执行其中的测试函数。

  1. 配置 pytest:

如果需要对 pytest 进行配置,可以在项目根目录下创建一个 pytest.ini 文件,该文件可以包含一些配置选项,如:

[pytest]
addopts = -ra -q
testpaths = tests

这个例子中,addopts 设置了默认的命令行参数(显示简短的摘要信息和安静模式),testpaths 指定了包含测试文件的目录。

另外,你还可以在项目中创建 conftest.py 文件来定义 fixture 和插件,或者编写自定义的钩子函数。

通过这些步骤,你可以快速开始使用 pytest 进行测试。随着对 pytest 的进一步学习,你将能够掌握更多的高级功能和配置选项。

编写测试用例

当然,下面我将通过一个简单的例子来展示如何使用pytest编写测试用例。

假设我们有一个名为 calculator.py 的文件,里面包含一个简单的计算器类 Calculator,它有两个方法:add 和 multiply。

首先是我们的计算器代码 calculator.py:

# calculator.py

class Calculator:
    """一个简单的计算器类"""

    @staticmethod
    def add(a, b):
        """返回两个数的和"""
        return a + b

    @staticmethod
    def multiply(a, b):
        """返回两个数的乘积"""
        return a * b

接下来,我们将编写测试这个计算器类的测试用例。我们创建一个叫 test_calculator.py 的文件,并编写测试函数:

# test_calculator.py

from calculator import Calculator

def test_add():
    """测试 add 方法"""
    calc = Calculator()
    result = calc.add(2, 3)
    assert result == 5

def test_multiply():
    """测试 multiply 方法"""
    calc = Calculator()
    result = calc.multiply(2, 3)
    assert result == 6

在这个例子中,test_add 函数将测试 Calculator 类的 add 方法是否能正确计算两个数的和。同样,test_multiply 函数将测试 multiply 方法是否能正确计算两个数的乘积。

现在我们可以运行 pytest 来执行这些测试。在命令行中,确保你在包含 test_calculator.py 和 calculator.py 的目录中,然后运行:

pytest

Pytest 会自动找到以 test_ 开头的函数并执行它们。如果所有的测试都通过了,你会看到类似下面的输出:

============================= test session starts ==============================
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y
rootdir: /path/to/your/tests
collected 2 items

test_calculator.py ..                                                     [100%]

============================== 2 passed in 0.03s ===============================

这意味着你的两个测试用例都成功执行并通过了测试。如果有测试失败,pytest 会提供详细的失败报告,帮助你定位问题。

通过这个简单的例子,你可以看到 pytest 用起来是多么直观和简单。你可以根据需要编写更复杂的测试用例,包括设置测试数据、处理测试前后的配置以及参数化测试。

生成测试结果和报告

Pytest 在执行测试用例时,默认会在命令行中输出测试结果,包括每个测试用例的执行情况(成功、失败、跳过等)和整体的测试摘要。然而,在一些情况下,我们可能需要生成更详细和格式化的测试报告。Pytest 提供了多种方式来生成测试结果和报告。

命令行输出:

默认情况下,当你运行 pytest 命令时,测试结果会直接打印在控制台上。Pytest 会清晰地展示哪些测试通过了,哪些失败了,以及失败的原因。

内置的报告生成:

Pytest 支持使用 --junitxml 标志生成一个 JUnit 风格的 XML 文件,这个文件可以被许多 CI/CD 系统识别,用于进一步分析和呈现测试结果。

例如:

pytest --junitxml=path/to/report.xml

这条命令会运行测试并创建一个名为 report.xml 的文件,其中包含了测试结果的详细信息。

HTML 报告:

要生成 HTML 格式的报告,你通常需要使用 pytest-html 插件,它不是内置的,必须单独安装:

pip install pytest-html

然后,你可以通过添加 --html 参数来指定 HTML 报告的生成:

pytest --html=path/to/report.html

这将创建一个 report.html 文件,它会以 HTML 格式展示测试结果,包括通过的、失败的和跳过的测试。

测试覆盖率报告:

可以使用 pytest-cov 插件来生成测试覆盖率报告。首先,你需要安装这个插件:

pip install pytest-cov

然后,使用 --cov 参数来运行测试并生成覆盖率报告:

pytest --cov=my_package

如果你还想要生成一个 HTML 格式的覆盖率报告,可以使用:

pytest --cov=my_package --cov-report html:path/to/cov_html

这条命令将在指定的目录中创建一个包含测试覆盖率详情的 HTML 报告。

通过这些方法,你可以根据项目的需要生成不同格式的测试报告。通常在持续集成流程中,会自动运行测试并生成这些报告,以便于团队成员查看测试结果和测试覆盖率。

高级特性

Fixture参数化

Fixture 参数化是 pytest 的一个强大特性,它允许你为 fixtures 提供多个参数值,从而可以用不同的设置运行相同的测试代码。下面是一个展示如何使用 fixture 参数化的例子。

假设我们正在测试一个简单的电子邮件格式验证器。首先,我们定义一个名为 validate_email 的函数,用于检查电子邮件地址是否有效。

# email_validator.py

import re

def validate_email(email):
    """验证是否为有效电子邮件地址。"""
    if re.match(r"[^@]+@[^@]+\.[^@]+", email):
        return True
    return False

接下来,我们编写一个带有 fixture 参数化的测试用例。我们将创建一个 fixture 函数,它返回不同的电子邮件地址以及它们是否应该通过验证的预期结果。

# test_email_validator.py
import pytest
from email_validator import validate_email

# 使用 pytest.fixture 装饰器进行 fixture 定义,并使用 params 参数传递一个包含多个元组的列表。
# 每个元组包含一对电子邮件地址和预期的验证结果。
@pytest.fixture(params=[
    ("test@example.com", True),      # 有效的电子邮件地址
    ("testexample.com", False),      # 缺少 @ 符号
    ("@example.com", False),         # 缺少用户名
    ("test@", False),                # 缺少域名
    ("", False),                     # 空字符串
])
def email_fixture(request):
    """返回电子邮件地址及其预期的验证结果。"""
    return request.param

# 我们的测试函数 test_validate_email 接收 email_fixture 作为参数。
# Pytest 将会为参数化的 fixture 的每个参数值运行一次这个测试函数。
def test_validate_email(email_fixture):
    email, expected = email_fixture
    assert validate_email(email) == expected

在这个例子中,email_fixture fixture 被参数化了,所以 test_validate_email 会使用每个参数(即不同的电子邮件地址和预期结果)运行五次。

要运行这些测试,你只需在包含 test_email_validator.py 文件的目录中执行 pytest 命令。Pytest 将会自动识别并执行这些参数化的测试,对电子邮件格式验证逻辑进行彻底的检查。

Fixture的作用域

Pytest fixtures 提供了一种为测试函数设置初始化数据或状态的机制。Fixture 的作用域(scope)决定了该 fixture 被实例化和销毁的频率。下面是一个使用 fixture 作用域的例子。

假设我们有一个数据库连接的 fixture,并且想要根据不同的测试需求来设置它的作用域。以下是一个简单的模拟示例:

首先,我们假设有一个模拟数据库连接的类:

# db.py

class Database:
    """简单的数据库连接模拟类。"""

    def __init__(self):
        self.connected = False

    def connect(self):
        """模拟建立数据库连接。"""
        self.connected = True
        print("Database connected.")

    def disconnect(self):
        """模拟断开数据库连接。"""
        self.connected = False
        print("Database disconnected.")

    def query(self):
        """模拟数据库查询。"""
        if self.connected:
            return "Query data"
        else:
            raise ValueError("Not connected to the database.")

接下来,我们可以创建一个 fixture,并设置它的作用域。Pytest 允许你设置四种作用域:function(默认)、class、module、session。

  1. function - fixture 为每个测试函数实例化一次。
  2. class - fixture 为每个测试类实例化一次,无论它有多少个测试方法。
  3. module - fixture 为每个模块(文件)实例化一次,无论模块中有多少测试函数或类。
  4. session - fixture 在整个测试会话期间只实例化一次,无论有多少测试模块。
    下面是如何定义一个数据库连接的 fixture,并设置作用域为 module:
# test_db.py
import pytest
from db import Database

@pytest.fixture(scope="module")
def db():
    """模块范围内的数据库连接 fixture。"""
    database = Database()
    database.connect()
    yield database
    database.disconnect()

def test_query1(db):
    assert db.query() == "Query data"

def test_query2(db):
    assert db.query() == "Query data"

在这个例子中,db fixture 被设置成 module 作用域。这意味着无论 test_query1 或 test_query2 测试函数何时运行,数据库连接仅在模块层面上建立和断开一次。这常用于设置成本较高的资源,如数据库连接、网络会话等,可以在测试模块中重复使用而不必频繁开启和关闭。

如果你有多个测试模块都需要使用数据库连接,并且你希望在所有测试开始时只连接一次数据库,在所有测试结束时断开连接,那么可以使用 session 作用域。

运行 pytest 时,会看到 “Database connected.” 和 “Database disconnected.” 消息各打印一次,表明数据库连接的确仅在模块层面上被创建和销毁了。这样,我们就能够确保资源的有效利用和测试的高效性。

参考文档

  • 13
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在Python中,pytest是一个用于编写和运行测试的框架。它提供了丰富的功能和灵活的语法,使得编写和管理测试变得更加简单和高效。以下是一个简单的pytest教程,帮助您入门: 1. 安装pytest: 您可以使用pip命令安装pytest框架。打开终端或命令提示符,并执行以下命令: ``` pip install pytest ``` 2. 编写测试函数: 在您的项目中创建一个测试文件(通常以`test_`开头),并在其中定义测试函数。每个测试函数应以`test_`开头,并且可以使用各种pytest提供的断言函数来检查期望的结果。 ```python def test_addition(): assert 2 + 2 == 4 def test_subtraction(): assert 5 - 3 == 2 ``` 3. 运行测试: 打开终端或命令提示符,并导航到包含测试文件的目录。然后执行以下命令来运行所有的测试函数: ``` pytest ``` pytest会自动发现并执行所有以`test_`开头的函数,并输出测试结果。 4. 断言函数: pytest提供了丰富的断言函数来检查期望的结果。一些常用的断言函数包括: - `assert expression`:断言表达式为真。 - `assert condition == expected_result`:断言条件与期望结果相等。 - `assert condition != expected_result`:断言条件与期望结果不相等。 - `assert condition in container`:断言条件存在于容器中。 - `assert condition not in container`:断言条件不存在于容器中。 您可以根据需要选择合适的断言函数来编写测试。 这只是一个简单的pytest教程,让您快速入门。pytest还提供了许多其他功能,如参数化测试测试装置等,您可以根据需要进一步探索和学习。希望对您有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

namedlock

您的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值