如何使用基于 unittest
的测试与 pytest¶
pytest
支持开箱即用地运行基于 Python unittest
的测试。它的目的是利用现有的基于 unittest
的测试套件,将 pytest 用作测试运行器,并允许逐步调整测试套件,以充分利用 pytest 的功能。
要使用 pytest
运行现有的 unittest
风格的测试套件,请键入
pytest tests
pytest 会自动收集 unittest.TestCase
子类及其在 test_*.py
或 *_test.py
文件中的 test
方法。
几乎所有 unittest
功能都受支持
@unittest.skip
样式装饰器;setUp/tearDown
;setUpClass/tearDownClass
;setUpModule/tearDownModule
;
此外,子测试 受 pytest-subtests 插件支持。
到目前为止,pytest 不支持以下功能
开箱即用的好处¶
通过使用 pytest 运行测试套件,你可以利用多项功能,在大多数情况下无需修改现有代码
获取 更具信息性的回溯;
stdout 和 stderr 捕获;
测试选择选项,使用
-k
和-m
标志;使用 pytest-xdist 插件将测试分发到多个 CPU;
使用 普通 assert 语句,而不是
self.assert*
函数(unittest2pytest 在这方面非常有帮助);
unittest.TestCase
子类中的 pytest 功能¶
以下 pytest 功能在 unittest.TestCase
子类中有效
以下 pytest 功能不起作用,并且由于不同的设计理念,可能永远不会起作用
第三方插件可能无法正常工作,具体取决于插件和测试套件。
使用标记将 pytest 固定装置混合到 unittest.TestCase
子类中¶
使用 pytest
运行 unittest 时,您可以将其 固定装置机制 与 unittest.TestCase
样式测试结合使用。假设您至少浏览过 pytest 固定装置功能,那么让我们直接跳到一个示例中,该示例集成了一个 pytest db_class
固定装置,设置一个类缓存的数据库对象,然后从 unittest 样式测试中引用它
# content of conftest.py
# we define a fixture function below and it will be "used" by
# referencing its name from tests
import pytest
@pytest.fixture(scope="class")
def db_class(request):
class DummyDB:
pass
# set a class attribute on the invoking test context
request.cls.db = DummyDB()
这定义了一个固定装置函数 db_class
,如果使用,则对每个测试类调用一次,并将类级别的 db
属性设置为 DummyDB
实例。固定装置函数通过接收一个特殊的 request
对象来实现此目的,该对象可以访问 请求的测试上下文,例如 cls
属性,表示使用固定装置的类。此架构将固定装置编写与实际测试代码解耦,并允许通过最少的引用(固定装置名称)重复使用固定装置。因此,让我们使用我们的固定装置定义编写一个实际的 unittest.TestCase
类
# content of test_unittest_db.py
import unittest
import pytest
@pytest.mark.usefixtures("db_class")
class MyTest(unittest.TestCase):
def test_method1(self):
assert hasattr(self, "db")
assert 0, self.db # fail for demo purposes
def test_method2(self):
assert 0, self.db # fail for demo purposes
类装饰器 @pytest.mark.usefixtures("db_class")
确保每个类调用一次 pytest 固定装置函数 db_class
。由于故意失败的 assert 语句,我们可以在回溯中查看 self.db
值
$ pytest test_unittest_db.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items
test_unittest_db.py FF [100%]
================================= FAILURES =================================
___________________________ MyTest.test_method1 ____________________________
self = <test_unittest_db.MyTest testMethod=test_method1>
def test_method1(self):
assert hasattr(self, "db")
> assert 0, self.db # fail for demo purposes
E AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
E assert 0
test_unittest_db.py:11: AssertionError
___________________________ MyTest.test_method2 ____________________________
self = <test_unittest_db.MyTest testMethod=test_method2>
def test_method2(self):
> assert 0, self.db # fail for demo purposes
E AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
E assert 0
test_unittest_db.py:14: AssertionError
========================= short test summary info ==========================
FAILED test_unittest_db.py::MyTest::test_method1 - AssertionError: <conft...
FAILED test_unittest_db.py::MyTest::test_method2 - AssertionError: <conft...
============================ 2 failed in 0.12s =============================
此默认 pytest 回溯显示两个测试方法共享相同的 self.db
实例,这是我们在编写上述类范围固定装置函数时的意图。
使用 autouse 固定装置并访问其他固定装置¶
虽然通常最好明确声明您对特定测试所需的固定装置,但有时您可能希望在给定上下文中自动使用固定装置。毕竟,unittest 设置的传统样式要求使用此隐式固定装置编写,并且您可能已经习惯或喜欢它。
您可以使用 @pytest.fixture(autouse=True)
标记固定装置函数,并在希望使用它的上下文中定义固定装置函数。让我们看一下 initdir
固定装置,它使 TestCase
类的所有测试方法在具有预先初始化的 samplefile.ini
的临时目录中执行。我们的 initdir
固定装置本身使用 pytest 内置 tmp_path
固定装置来委派创建每个测试的临时目录
# content of test_unittest_cleandir.py
import unittest
import pytest
class MyTest(unittest.TestCase):
@pytest.fixture(autouse=True)
def initdir(self, tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path) # change to pytest-provided temporary directory
tmp_path.joinpath("samplefile.ini").write_text("# testdata", encoding="utf-8")
def test_method(self):
with open("samplefile.ini", encoding="utf-8") as f:
s = f.read()
assert "testdata" in s
由于 autouse
标记, initdir
固定装置函数将用于定义它的类的所有方法。这相当于在类上使用 @pytest.mark.usefixtures("initdir")
标记,就像前面的示例中一样。
运行此测试模块 …
$ pytest -q test_unittest_cleandir.py
. [100%]
1 passed in 0.12s
… 为我们提供了一个通过的测试,因为 initdir
固定装置函数在 test_method
之前执行。
注意
unittest.TestCase
方法不能直接接收固定参数,因为实现该方法可能会影响运行通用 unittest.TestCase 测试套件的能力。
上面的 usefixtures
和 autouse
示例应有助于将 pytest 固定参数混合到 unittest 套件中。
您还可以逐渐放弃从 unittest.TestCase
子类化到纯断言,然后逐步开始受益于完整的 pytest 功能集。
注意
由于这两个框架之间的架构差异,unittest
基于测试的设置和拆除是在测试的 call
阶段执行的,而不是在 pytest
的标准 setup
和 teardown
阶段执行的。在某些情况下,了解这一点很重要,尤其是在推理错误时。例如,如果 unittest
基于套件在设置期间出现错误,pytest
将在其 setup
阶段报告没有错误,而是在 call
期间引发错误。