入门¶
Get Started
安装 pytest
¶
Install pytest
pytest
需要:Python 3.8+ 或 PyPy3。
在命令行中运行以下命令:
pip install -U pytest
检查是否安装了正确的版本:
$ pytest --version
pytest 8.3.3
pytest
requires: Python 3.8+ or PyPy3.
Run the following command in your command line:
pip install -U pytest
Check that you installed the correct version:
$ pytest --version
pytest 8.3.3
创建你的第一个测试¶
Create your first test
创建一个名为 test_sample.py
的新文件,其中包含一个函数和一个测试:
# 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
。
Note
您可以使用 assert
语句来验证测试预期。pytest 的 高级断言自省 将智能地报告断言表达式的中间值,这样您就可以避免 JUnit 遗留方法 的许多名称。
Create a new file called test_sample.py
, containing a function, and a test:
# content of test_sample.py
def func(x):
return x + 1
def test_answer():
assert func(3) == 5
The test
$ 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 =============================
The [100%]
refers to the overall progress of running all test cases. After it finishes, pytest then shows a failure report because func(3)
does not return 5
.
Note
You can use the assert
statement to verify test expectations. pytest’s Advanced assertion introspection will intelligently report intermediate values of the assert expression so you can avoid the many names of JUnit legacy methods.
运行多个测试¶
Run multiple tests
pytest
将在当前目录及其子目录中运行所有形式为 test_*.py 或 *_test.py 的文件。更一般地,它遵循: 标准测试发现规则。
pytest
will run all files of the form test_*.py or *_test.py in the current directory and its subdirectories. More generally, it follows standard test discovery rules.
断言引发了某个异常¶
Assert that a certain exception is raised
使用 raises 辅助程序断言某些代码引发了异常:
# test_sysexit.py的内容
import pytest
def f():
raise SystemExit(1)
def test_mytest():
with pytest.raises(SystemExit):
f()
您还可以使用 raises 提供的上下文来断言预期异常是引发的 ExceptionGroup
的一部分:
# 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)
使用“安静(quiet)”报告模式执行测试函数:
$ pytest -q test_sysexit.py
. [100%]
1 passed in 0.12s
Note
-q/--quiet
标志可在此示例和后续示例中保持简短的输出。
Use the raises helper to assert that some code raises an exception:
# content of test_sysexit.py
import pytest
def f():
raise SystemExit(1)
def test_mytest():
with pytest.raises(SystemExit):
f()
You can also use the context provided by raises to
assert that an expected exception is part of a raised 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)
Execute the test function with “quiet” reporting mode:
$ pytest -q test_sysexit.py
. [100%]
1 passed in 0.12s
Note
The -q/--quiet
flag keeps the output brief in this and following examples.
在一个类(class)中对多个测试进行分组¶
Group multiple tests in a class
一旦您开发了多个测试,您可能希望将它们分组到一个类中。pytest 使得创建包含多个测试的类变得简单:
# 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
========================= 测试摘要信息 ==========================
FAILED test_class.py::TestClass::test_two - AssertionError: assert False
1 failed, 1 passed in 0.12s
第一个测试通过,第二个测试失败。您可以轻松查看断言中的中间值,以帮助您理解失败的原因。
将测试分组到类中可以带来以下好处:
测试组织
仅在特定类中共享夹具
在类级别应用标记,并使其隐式适用于所有测试
在类中分组测试时需要注意的是,每个测试都有一个唯一的类实例。 让每个测试共享相同的类实例将对测试隔离产生非常不利的影响,并会促进不良的测试实践。 这一点在下面阐述:
# 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
========================= 测试摘要信息 ==========================
FAILED test_class_demo.py::TestClassDemoInstance::test_two - assert 0 == 1
1 failed, 1 passed in 0.12s
请注意,在类级别添加的属性是 类属性(class attributes),因此它们将在测试之间共享。
Once you develop multiple tests, you may want to group them into a class. pytest makes it easy to create a class containing more than one test:
# 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
discovers all tests following its Conventions for Python test discovery, so it finds both test_
prefixed functions. There is no need to subclass anything, but make sure to prefix your class with Test
otherwise the class will be skipped. We can simply run the module by passing its filename:
$ 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
The first test passed and the second failed. You can easily see the intermediate values in the assertion to help you understand the reason for the failure.
Grouping tests in classes can be beneficial for the following reasons:
Test organization
Sharing fixtures for tests only in that particular class
Applying marks at the class level and having them implicitly apply to all tests
Something to be aware of when grouping tests inside classes is that each test has a unique instance of the class. Having each test share the same class instance would be very detrimental to test isolation and would promote poor test practices. This is outlined below:
# 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
Note that attributes added at class level are class attributes, so they will be shared between tests.
为功能测试请求唯一的临时目录¶
Request a unique temporary directory for functional tests
pytest
提供了 内置夹具/函数参数 来请求任意资源,比如一个唯一的临时目录:
# 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
有关临时目录处理的更多信息,请参见 临时目录和文件。
使用以下命令查找存在的内置 pytest 夹具 的类型:
pytest --fixtures # 显示内置和自定义夹具
请注意,此命令省略以 _
开头的夹具,除非添加了 -v
选项。
pytest
provides Builtin fixtures/function arguments to request arbitrary resources, like a unique temporary directory:
# content of test_tmp_path.py
def test_needsfiles(tmp_path):
print(tmp_path)
assert 0
List the name tmp_path
in the test function signature and pytest
will lookup and call a fixture factory to create the resource before performing the test function call. Before the test runs, pytest
creates a unique-per-test-invocation temporary directory:
$ 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
More info on temporary directory handling is available at Temporary directories and files.
Find out what kind of builtin pytest fixtures exist with the command:
pytest --fixtures # shows builtin and custom fixtures
Note that this command omits fixtures with leading _
unless the -v
option is added.
继续阅读¶
Continue reading
查看其他 pytest 资源,以帮助您根据自己的独特工作流程自定义测试:
“如何调用 pytest” 了解命令行调用示例
“如何将 pytest 与现有测试套件结合使用” 了解使用现有测试的情况
“如何用属性标记测试函数” 了解
pytest.mark
机制的信息“Fixtures 参考” 为您的测试提供功能基准
“编写插件” 了解管理和编写插件的情况
“良好的集成实践” 了解虚拟环境和测试布局的情况
Check out additional pytest resources to help you customize tests for your unique workflow:
“如何调用 pytest” for command line invocation examples
“如何将 pytest 与现有测试套件结合使用” for working with preexisting tests
“如何用属性标记测试函数” for information on the
pytest.mark
mechanism“Fixtures 参考” for providing a functional baseline to your tests
“编写插件” for managing and writing plugins
“良好的集成实践” for virtualenv and test layouts