pytest 是一个强大的 Python 测试框架,可让您轻松创建小型、简单的测试,同时可扩展以支持应用程序和库的复杂功能测试。
它为您提供了一个平台,通过将测试用例编写为函数来执行 Python 测试,从而减少代码和样板代码的数量。
它还可以轻松创建测试套件或测试用例集合,用于测试 Python 代码的特定方面。
此外,pytest 提供了处理测试中常见任务的工具,例如测试的设置和分组。
安装Python pytest
要开始使用 pytest,您首先需要安装它。您可以使用 pip 安装 pytest,pip 是标准库附带的 Python 包安装程序。
这是安装 pytest 的命令:
pip install pytest
运行此命令后,pytest 应该已安装在您的 Python 环境中,您可以通过检查已安装的 pytest 版本来验证这一点:
pytest --version
您可能会看到类似于以下内容的输出:
pytest 7.3.1
这说明pytest已经安装成功,安装的版本是7.3.1。
现在,您已准备好开始使用 pytest 来满足您的 Python 测试需求。
基本 pytest 术语
在深入研究 pytest 之前,我们首先了解一些基本术语:
-
测试用例:这是一个测试单元。它检查对一组特定输入的特定响应。在 pytest 中,测试用例是一个以关键字开头的 Python 函数
test
.
-
测试套件:这是测试用例、测试套件或两者的集合。它用于聚合应一起执行的测试。
-
测试功能:它是测试特定代码单元的单个函数。
-
单元测试:这是软件测试的一个级别,其中测试软件的各个单元/组件。目的是验证软件的每个单元是否按设计运行。 pytest 提供了一种在 Python 中进行单元测试的简单方法。
这是一个测试套件的示例,它由两个测试用例(测试函数)组成:
import pytest
def test_addition():
assert 1 + 1 == 2
def test_subtraction():
assert 3 - 2 == 1
运行此测试套件时,pytest 识别两个测试函数并运行它们。输出可能是:
test_module.py::test_addition PASSED
test_module.py::test_subtraction PASSED
此输出表明我们的测试套件中的两个测试函数都通过了各自的测试。
Pytest 命名约定
pytest中的命名约定不仅是为了保持代码的一致性和整洁,也是为了确保测试发现机制的功能。
pytest 框架利用这些约定来自动识别和运行测试。
以下是 pytest 中使用的一些基本命名约定:
测试文件
pytest 中的测试发现过程从查找测试文件开始。按照惯例,所有测试文件都应命名为test_*.py
or *_test.py
。 Pytest 会自动将它们识别为包含测试的文件。
测试功能和方法
同样,所有测试函数都应以test_
, 例如,test_example()
。如果您在类中定义测试方法,它们也应该遵循此命名约定。
测试班
定义测试类时,该类应以Test
,并且不应该有__init__
方法。例如,TestClassExample
.
夹具功能
夹具功能提供固定的基线,以便测试可靠地执行并产生一致的结果。它们被命名为常规函数,但应该描述它们提供的特定状态。例如,def setup_database()
or def login_client()
.
尽管 pytest 不区分大小写,但 Python 命名约定建议使用小写单词并用下划线分隔。这对于保持测试文件的可读性尤其重要。
请记住,遵循这些约定的重要性不仅仅是“规则”。正确命名您的测试和类有助于确保 pytest 能够正确发现并运行您的测试和固定装置。
运行你的第一个测试
创建测试函数后,导航到包含测试文件的目录并运行 pytest 命令。
例如,考虑以下测试用例:
import pytest
def test_addition():
assert 1 + 1 == 2
要运行此测试,请导航到包含测试文件的目录并运行:
pytest -v
您应该看到类似于以下内容的输出:
============================= test session starts ==============================
...
test_module.py::test_addition PASSED
========================== 1 passed in 0.03 seconds ===========================
“1 通过”消息是总体状态报告,指示通过的测试数量。
The -v
命令行选项代表 verbose,意味着 pytest 将输出更详细的报告。
命令行选项
-
Specifying tests
-
-k EXPRESSION
:运行与给定关键字表达式匹配的测试。
-
-m MARKEXPR
:仅运行与给定标记表达式匹配的测试。
-
Test session
-
-x, --exitfirst
:第一次出错或测试失败时立即退出。
-
Output
-
-s
:在输出中显示打印语句(默认情况下,pytest捕获输出)。
-
-v, --verbose
:增加详细程度。
-
-q, --quiet
:减少冗长。
-
Test execution
-
--ff, --failed-first
:运行所有测试,但首先运行最后一次失败的测试。
-
-n numprocesses
:‘–dist=each –tx=NUM*popen’的快捷方式,其中 NUM 指的是要使用的核心数量。这来自于pytest-xdist
用于并行执行的插件。
-
Reporting
-
--html=path
:创建测试结果的 HTML 报告。这需要pytest-html
pytest 函数剖析
pytest 函数是一个以单词开头的 Python 函数test_
.
这就是 pytest 识别其测试的方式。这是 pytest 函数的一个简单示例:
import pytest
def test_addition():
assert 1 + 1 == 2
在此测试函数中,我们使用assert
关键字来指定我们要测试的条件。如果条件是True
,测试通过;如果False
,测试失败并提供失败的详细信息。
使用 pytest 运行此函数将输出:
============================= test session starts ==============================
...
test_module.py::test_addition PASSED
========================== 1 passed in 0.03 seconds ===========================
输出提供了一个状态报告,显示了我们的测试功能test_addition
已经过去了。
The assert
与其他测试框架相比,关键字是 pytest 可以编写更少代码的原因之一。
使用断言
在 pytest 中,断言是由assert
语句,它检查给定的逻辑表达式是否为真。如果不正确,测试将失败。
这是在 pytest 函数中使用断言的示例:
import pytest
def test_multiplication():
assert 2 * 3 == 6
使用 pytest 运行此测试函数,输出将是:
============================= test session starts ==============================
...
test_module.py::test_multiplication PASSED
========================== 1 passed in 0.03 seconds ===========================
在这个测试函数中,assert
语句用于检查 2 乘以 3 是否等于 6。
事实上,测试通过了,并且 pytest 为我们提供了该测试的“PASSED”输出。
但是,如果断言是错误的,pytest 将提供有用的失败解释。例如,让我们改变我们的测试函数:
import pytest
def test_multiplication():
assert 2 * 3 == 7
使用 pytest 运行此测试函数,输出将是:
============================= test session starts ==============================
...
test_module.py::test_multiplication FAILED
================================ FAILURES =================================
______________________________ test_multiplication ______________________________
def test_multiplication():
> assert 2 * 3 == 7
E assert (2 * 3) == 7
test_module.py:4: AssertionError
========================== 1 failed in 0.03 seconds ===========================
在这种情况下,pytest 准确显示断言失败的位置和原因,并提供失败的详细信息。这使得调试和修复测试变得更加容易。
使用 pytest 标记
Pytest 标记允许我们轻松地在测试函数上设置元数据,例如将测试标记为预期失败,或者在某些条件下应跳过它们。
跳过测试
例如,您可以使用skip
标记跳过特定测试:
import pytest
@pytest.mark.skip(reason="Skip this test")
def test_addition():
assert 1 + 1 == 2
当您使用 pytest 运行此测试时,输出将是:
============================= test session starts ==============================
...
test_module.py::test_addition SKIPPED
========================== 1 skipped in 0.02 seconds ===========================
预期失败
The xfail
标记表明测试预计会失败:
import pytest
@pytest.mark.xfail
def test_failed_addition():
assert 1 + 1 == 3
使用 pytest 运行它,输出将是:
============================= test session starts ==============================
...
test_module.py::test_failed_addition XFAIL
========================== 1 xfailed in 0.02 seconds ===========================
标记和过滤测试
您还可以创建自己的自定义标记。例如,您可以将某些测试标记为仅在特定平台上运行:
import pytest
import sys
@pytest.mark.windows
def test_windows_only_behavior():
assert sys.platform == 'win32'
@pytest.mark.linux
def test_linux_only_behavior():
assert sys.platform == 'linux'
然后,您可以仅运行具有特定标记的测试:
pytest -m windows
The -m
flag 允许您运行用给定标记名称标记的测试。
处理pytest中的异常
pytest 提供了多种方法来断言引发异常。实现这一点的主要工具是pytest.raises
函数,可用于测试某段代码是否引发特定异常。
举个例子:
import pytest
def test_division_by_zero():
with pytest.raises(ZeroDivisionError):
num = 1 / 0
在此测试函数中,我们正在测试除以零会产生ZeroDivisionError
。使用 pytest 运行它会输出:
============================= test session starts ==============================
...
test_module.py::test_division_by_zero PASSED
========================== 1 passed in 0.03 seconds ===========================
测试是成功的,因为除以零确实会产生ZeroDivisionError
.
测试警告
要测试警告,您可以使用pytest.warns
。这是一个例子:
import pytest
import warnings
def test_warning():
with pytest.warns(UserWarning):
warnings.warn("This is a warning!", UserWarning)
当您使用 pytest 运行此测试时,输出将是:
============================= test session starts ==============================
...
test_module.py::test_warning PASSED
========================== 1 passed in 0.03 seconds ===========================
在此测试中,我们检查我们的代码是否发出UserWarning
。测试成功,因为我们的代码确实发出了UserWarning
.
这些是 pytest 帮助测试异常和警告的一些方法。
pytest 夹具
pytest 夹具是在应用它的每个测试函数之前运行的函数。当我们想在每个测试方法之前运行一些代码时,就会使用夹具。
让我们从一个简单的装置开始:
import pytest
@pytest.fixture
def input_value():
input = 39
return input
def test_division(input_value):
assert input_value % 3 == 0
在这里,我们定义了一个固定功能input_value()
返回值 39。然后我们在中使用这个装置test_division()
将其作为参数传递。
pytest 知道input_value
是一个固定装置并调用它,然后将返回值传递给测试。
使用 pytest 运行将输出:
============================= test session starts ==============================
...
test_module.py::test_division PASSED
========================== 1 passed in 0.03 seconds ===========================
使用内置 pytest 夹具
pytest 还提供了几个内置装置。例如,tmpdir
Fixture 提供了测试调用所特有的临时目录:
def test_create_file(tmpdir):
p = tmpdir.mkdir("sub").join("hello.txt")
p.write("content")
assert p.read() == "content"
夹具范围
夹具功能的范围可以通过提供scope
参数到@pytest.fixture
装饰师。
例如,如果您想创建一个每个模块初始化一次的固定装置,您可以使用module
scope:
import pytest
@pytest.fixture(scope="module")
def input_value():
input = 39
return input
该装置每个模块仅执行一次。其他可能的值scope
are function
, class
, and session
.
pytestconftest.py
conftest.py
是一个本地插件,用于共享固定功能、挂钩、插件和 pytest 的其他配置。
它会被 pytest 自动发现,因此您无需将其导入到测试文件中。名字conftest
代表配置测试。
conftest.py
应位于项目的根目录或包含测试文件的目录中。 pytest 会找到conftest.py
测试收集时的文件。
一种常见的用法是conftest.py
is to 存储 pytest 夹具函数可以在多个测试文件中使用。例如,考虑以下固定功能:
# conftest.py
import pytest
@pytest.fixture
def csv_data():
return [
{"name": "John", "age": 30, "job": "developer"},
{"name": "Jane", "age": 25, "job": "designer"},
]
This csv_data
固定装置现在可以在同一目录中的任何测试文件中使用conftest.py
或任何子目录。这是使用此夹具的测试文件的示例:
# test_sample.py
def test_csv_data(csv_data):
for row in csv_data:
assert isinstance(row["name"], str)
assert isinstance(row["age"], int)
assert isinstance(row["job"], str)
使用 pytest 运行这个测试,你会发现csv_data
夹具来自conftest.py
自动使用:
============================= test session starts ==============================
...
test_sample.py::test_csv_data PASSED
========================== 1 passed in 0.03 seconds ===========================
将测试分组到班级
pytest 允许您将多个测试分组,这些测试都做出类似的断言到一个类中。这有助于让您的测试井井有条,并允许您共享设置或夹具代码。
以下是如何使用类对测试进行分组的示例:
import pytest
class TestMathOperations:
def test_addition(self):
assert 1 + 1 == 2
def test_subtraction(self):
assert 3 - 2 == 1
使用 pytest 运行它,输出将如下所示:
============================= test session starts ==============================
...
test_module.py::TestMathOperations::test_addition PASSED
test_module.py::TestMathOperations::test_subtraction PASSED
========================== 2 passed in 0.04 seconds ===========================
在输出中,我们看到两个测试都已被识别并在以下环境下运行TestMathOperations
测试课。这样,随着我们的测试套件的增长,我们可以保持测试结构良好且可维护。
参数化测试
pytest 中的参数化测试允许您使用不同的参数多次运行测试函数。这是使用以下方法完成的@pytest.mark.parametrize
装饰师。
参数化有助于测试函数针对各种输入和预期结果的行为,而无需编写多个测试用例。
这是参数化测试的一个简单示例:
import pytest
@pytest.mark.parametrize("test_input,expected", [(3, 9), (2, 4), (6, 36)])
def test_calc_square(test_input, expected):
assert test_input**2 == expected
在这个例子中,test_calc_square
运行了三遍。每次,它都会使用一组不同的值test_input
and expected
.
使用 pytest 运行将输出:
============================= test session starts ==============================
...
test_module.py::test_calc_square[3-9] PASSED
test_module.py::test_calc_square[2-4] PASSED
test_module.py::test_calc_square[6-36] PASSED
========================== 3 passed in 0.04 seconds ===========================
输出中的每一行代表一个带有一组不同参数的测试函数调用。
这项技术对于减少测试中的样板代码数量并使它们更具可读性和可维护性非常有用。
pytest 模拟
有时在单元测试中,我们希望替换被测系统的某些部分,以将其与系统的其余部分隔离。
这个过程称为mocking,Python的标准库,unittest.mock
,为其提供了强大的框架。
以下是如何在 pytest 中使用模拟的示例:
from unittest.mock import MagicMock
import pytest
def test_magic_mock():
mock = MagicMock()
mock.__str__.return_value = 'foobarbaz'
assert str(mock) == 'foobarbaz'
在这个例子中,我们创建一个MagicMock
实例并指定返回值__str__
方法被调用。
然后测试断言此行为按预期工作。使用 pytest 运行此测试,输出将是:
============================= test session starts ==============================
...
test_module.py::test_magic_mock PASSED
========================== 1 passed in 0.03 seconds ===========================
pytest 配置(通过 pytest.ini)
可以通过名为的配置文件将 pytest 配置为设置默认行为和变量pytest.ini
.
该文件需要位于项目的根目录中,pytest会自动发现并使用它。
这是一个例子pytest.ini
file:
[pytest]
minversion = 7.0
addopts = -ra -q
testpaths =
tests
在这个例子中,minversion
指定运行测试所需的最低 pytest 版本。addopts
是 pytest 在运行时默认使用的附加选项。
Here, -ra
意味着 pytest 将在测试会话结束时打印所有未通过测试的摘要,并且-q
意味着 pytest 将减少冗长。
testpaths
是 pytest 将查找测试的目录列表。
当您的测试套件的大小和复杂性不断增加时,此文件非常有用,因为它可以存储 pytest 的默认行为和配置,从而使您的测试过程更加高效。
生成测试报告
使用 pytest 运行测试后,生成结果报告通常很有帮助。 pytest 允许您使用各种插件来做到这一点。
pytest-html 就是这样的插件之一,它会生成 HTML 报告。您可以使用 pip 安装它:
pip install pytest-html
安装后,您可以使用以下命令生成 HTML 报告--html
option:
pytest --html=report.html
运行此命令将在当前目录中创建一个名为“report.html”的 HTML 报告。
该报告将提供每个失败测试用例的失败细目、测试会话的总体状态报告以及各种其他信息。
另一个有用的插件是 pytest-cov,它提供测试覆盖率报告:
pip install pytest-cov
安装后,您可以使用它生成覆盖率报告:
pytest --cov=myproject
此命令将输出“myproject”目录的覆盖率报告,指示测试会话期间执行的代码的百分比。
这些只是 pytest 如何生成有用报告的几个示例。
Pytest 插件
pytest 插件是扩展 pytest 功能的一种方式。这些插件可以使用 pip 从 Python 包索引 (PyPI) 安装,并通过简单地将它们导入到测试代码中或在命令行上指定它们来使用。
以下是安装 pytest-xdist 插件的方法,该插件允许并行和分布式测试:
pip install pytest-xdist
要使用该插件,您可以在运行 pytest 时在命令行中指定它:
pytest -n 4
在此命令中,-n
是 pytest-xdist 提供的一个选项,并且4
是用于测试的 CPU 核心数。
流行的 pytest 插件
一些流行的 pytest 插件包括:
-
pytest-xdist
:用于并行和分布式测试。
-
pytest-cov
:用于测量测试覆盖率。
-
pytest-mock
:为了更方便地使用unittest.mock
module.
-
pytest-html
:用于生成 HTML 报告。
您还可以编写自己的 pytest 插件。 pytest 插件只是一个定义一个或多个钩子函数的 Python 包。
这是一个简单插件的示例,它在测试会话开始时打印一条消息:
# content of myplugin.py
def pytest_sessionstart():
print("Starting test session")
要使用此插件,您可以使用以下命令在命令行上指定它-p
option:
pytest -p myplugin
结论
我们涵盖了广泛的主题,从 pytest 的基础知识到更高级的功能,如固定装置、参数化、模拟和插件。
我希望本教程对您有所帮助,并帮助您为 Python 项目编写可靠的测试。
请记住,良好的测试对于维护高质量代码和及早发现问题至关重要。
测试愉快!