目录
pytest
是一个可以使构建简单和可伸缩的测试变得容易的框架。测试具有表达性和可读性,不需要样板代码。几分钟后就可以开始对应用程序或库进行小的单元测试或复杂的功能测试。
安装pytest
pytest要求:Python 3.8+或PyPy3。
-
在命令行中执行如下命令:
pip install -U pytest
-
检查是否安装了正确的版本:
$ pytest --version pytest 8.2.2
创建第一个测试
创建一个名为test_sample.py的新文件,包含一个普通函数和一个测试函数:
# content of test_sample.py
def func(x):
return x + 1
def test_answer():
assert func(3) == 5
执行测试:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
test_sample.py F [100%]
================================= FAILURES =================================
_______________________________ test_answer ________________________________
def test_answer():
> assert func(3) == 5
E assert 4 == 5
E + where 4 = func(3)
test_sample.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_sample.py::test_answer - assert 4 == 5
============================ 1 failed in 0.12s =============================
[100%]指的是运行所有测试用例的总体进度。完成后,pytest显示一个失败报告,因为func(3)没有返回5。
注意:
您可以使用assert语句来验证测试期望。pytest的高级断言自省将智能地报告断言表达式的中间值,这样您就可以避免使用JUnit遗留方法的许多名称。
运行多个测试
pytest将运行当前目录及其子目录中所有形式为test_*.py或*_test.py的文件。更一般地说,它遵循标准的测试发现规则。
断言引发了某个异常
使用raise帮助器来断言某些代码会引发异常:
# content of test_sysexit.py
import pytest
def f():
raise SystemExit(1)
def test_mytest():
with pytest.raises(SystemExit):
f()
您还可以使用 raises
提供的上下文来断言预期的异常是抛出的 ExceptionGroup
的一部分
# content of test_exceptiongroup.py
import pytest
def f():
raise ExceptionGroup(
"Group message",
[
RuntimeError(),
],
)
def test_exception_in_group():
with pytest.raises(ExceptionGroup) as excinfo:
f()
assert excinfo.group_contains(RuntimeError)
assert not excinfo.group_contains(TypeError)
f()
函数抛出了一个 ExceptionGroup
,ExceptionGroup
允许将多个异常作为一个组一起抛出,这在处理并行操作或需要同时报告多个错误时非常有用。在 test_exception_in_group()
测试函数中,使用 pytest
的 raises
上下文管理器来捕获 ExceptionGroup
异常。
assert excinfo.group_contains(RuntimeError)
:这个断言检查捕获的异常组中是否包含 RuntimeError
类型的异常。这里的 group_contains
方法用于检查异常组中是否包含特定类型的异常。这个断言应该成功,因为 f()
函数抛出的 ExceptionGroup
中确实包含了一个 RuntimeError
。assert not excinfo.group_contains(TypeError)
:这个断言检查捕获的异常组中是否不包含 TypeError
类型的异常。同样地,group_contains
是一个假设存在的方法。这个断言应该成功,因为 f()
函数抛出的 ExceptionGroup
中没有包含 TypeError
。
将多个测试分组在一个类中
一旦您开发了多个测试,您可能希望将它们分组到一个类中。Pytest可以很容易地创建一个包含多个测试的类:
# content of test_class.py
class TestClass:
def test_one(self):
x = "this"
assert "h" in x
def test_two(self):
x = "hello"
assert hasattr(x, "check")
pytest
会根据它的 Python 测试发现约定来发现所有测试,因此它会找到所有以 test_
为前缀的函数。你不需要继承任何特定的类,但请确保你的测试类以 Test
开头,否则这个类将会被跳过。我们可以简单地通过传递模块的文件名来运行测试模块。
$ pytest -q test_class.py
.F [100%]
================================= FAILURES =================================
____________________________ TestClass.test_two ____________________________
self = <test_class.TestClass object at 0xdeadbeef0001>
def test_two(self):
x = "hello"
> assert hasattr(x, "check")
E AssertionError: assert False
E + where False = hasattr('hello', 'check')
test_class.py:8: AssertionError
========================= short test summary info ==========================
FAILED test_class.py::TestClass::test_two - AssertionError: assert False
1 failed, 1 passed in 0.12s
注:-q/——quiet标志使输出保持简短。
第一个测试通过了,而第二个测试失败了。你可以很容易地在断言中看到中间值,这有助于你理解失败的原因。
将测试分组到类中可以带来以下好处:
测试组织
仅为特定类中的测试共享夹具(fixtures)
在类级别应用标记,并让它们隐式地应用于所有测试
在将测试分组到类内部时需要意识到的一点是,每个测试都会有一个类的唯一实例。如果每个测试都共享同一个类实例,那么这将极大地损害测试的隔离性,并可能助长不良的测试实践。以下是对此的概述:
# content of test_class_demo.py
class TestClassDemoInstance:
value = 0
def test_one(self):
self.value = 1
assert self.value == 1
def test_two(self):
assert self.value == 1
$ pytest -k TestClassDemoInstance -q
.F [100%]
================================= FAILURES =================================
______________________ TestClassDemoInstance.test_two ______________________
self = <test_class_demo.TestClassDemoInstance object at 0xdeadbeef0002>
def test_two(self):
> assert self.value == 1
E assert 0 == 1
E + where 0 = <test_class_demo.TestClassDemoInstance object at 0xdeadbeef0002>.value
test_class_demo.py:9: AssertionError
========================= short test summary info ==========================
FAILED test_class_demo.py::TestClassDemoInstance::test_two - assert 0 == 1
1 failed, 1 passed in 0.12s
请注意,在类级别添加的属性是类属性,因此它们将在测试之间共享。pytest -k
是pytest
测试框架中的一个命令行选项,其作用是指定运行特定测试用例的模式
为功能测试请求唯一的临时目录
pytest
提供了内置夹具(fixtures)/函数参数,用于请求任意资源,比如一个唯一的临时目录。这对于需要写入文件或进行其他临时操作的测试来说非常有用。通过使用 pytest
的这个功能,开发者可以确保每个测试都运行在一个干净的环境中,避免了测试之间的潜在冲突。
# content of test_tmp_path.py
def test_needsfiles(tmp_path):
print(tmp_path)
assert 0
在测试函数签名中列出名称 tmp_path
,pytest
会在执行测试函数调用之前查找并调用一个夹具来创建该资源。在测试运行之前,pytest
会为每个测试调用创建一个唯一的临时目录:
$ pytest -q test_tmp_path.py
F [100%]
================================= FAILURES =================================
_____________________________ test_needsfiles ______________________________
tmp_path = PosixPath('PYTEST_TMPDIR/test_needsfiles0')
def test_needsfiles(tmp_path):
print(tmp_path)
> assert 0
E assert 0
test_tmp_path.py:3: AssertionError
--------------------------- Captured stdout call ---------------------------
PYTEST_TMPDIR/test_needsfiles0
========================= short test summary info ==========================
FAILED test_tmp_path.py::test_needsfiles - assert 0
1 failed in 0.12s
当你在测试函数的参数列表中包含 tmp_path
时,pytest
会自动识别这是一个特殊的参数,并在测试执行之前调用一个内部的夹具工厂来创建一个新的、唯一的临时目录,并将这个目录作为 tmp_path
参数的值传递给测试函数。这个临时目录对于测试来说是私有的,意味着每个测试都会获得自己的独立目录,从而避免了测试之间的文件或数据冲突。测试完成后,pytest
会负责清理这些临时目录,确保测试环境的整洁。
关于临时目录处理的更多信息,请参见“临时目录和文件”(Temporary directories and files)。
使用以下命令查找存在哪些类型的内置 pytest 夹具:
pytest --fixtures # shows builtin and custom fixtures
请注意,除非添加了 -v
选项,否则此命令会省略以 _
开头的夹具。
默认情况下,当你运行这个命令来查看所有可用的内置 pytest
夹具时,它会隐藏那些名称以 _
开头的夹具。这是因为在 Python 和 pytest
的命名习惯中,以 _
开头的名称通常被视为“受保护的”或“私有的”,意味着它们不应该被外部直接访问或使用。然而,如果你想要查看包括这些“私有”夹具在内的所有夹具,可以通过添加 -v
(或 --verbose
)选项来增加命令的详细程度,从而显示所有夹具,无论其名称是否以 _
开头。