如何在 pytest 中使用基于 unittest
的测试¶
pytest
开箱即用地支持运行基于 Python unittest
的测试。它旨在利用现有基于 unittest
的测试套件,将 pytest 作为测试运行器,并允许逐步调整测试套件以充分利用 pytest 的功能。
要使用 pytest
运行现有 unittest
风格的测试套件,请键入
pytest tests
pytest 将自动收集 test_*.py
或 *_test.py
文件中的 unittest.TestCase
子类及其 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 在这方面非常有帮助);
pytest 在 unittest.TestCase
子类中的功能¶
以下 pytest 功能在 unittest.TestCase
子类中有效
以下 pytest 功能**不**起作用,并且由于不同的设计理念,可能永远不会起作用
第三方插件可能工作良好,也可能不工作良好,具体取决于插件和测试套件。
使用标记将 pytest fixture 混入 unittest.TestCase
子类¶
使用 pytest
运行您的 unittest 允许您将它的 fixture 机制与 unittest.TestCase
风格的测试一起使用。假设您至少已略读过 pytest fixture 功能,让我们直接进入一个示例,它集成了 pytest db_class
fixture,设置了一个类缓存的数据库对象,然后从 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()
这定义了一个 fixture 函数 db_class
,如果使用它,它将为每个测试类调用一次,并将类级别的 db
属性设置为 DummyDB
实例。该 fixture 函数通过接收一个特殊的 request
对象来实现这一点,该对象可以访问请求测试上下文,例如 cls
属性,它表示使用该 fixture 的类。这种架构将 fixture 的编写与实际测试代码解耦,并通过最小的引用(即 fixture 名称)实现 fixture 的重用。所以,让我们使用我们的 fixture 定义来编写一个实际的 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 fixture 函数 db_class
为每个类调用一次。由于故意失败的断言语句,我们可以在回溯中查看 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
实例,这正是我们上面编写类作用域 fixture 函数时的意图。
使用 autouse fixture 并访问其他 fixture¶
尽管通常最好明确声明给定测试所需的 fixture,但有时您可能希望在特定上下文中自动使用 fixture。毕竟,传统的 unittest 设置风格要求使用这种隐式 fixture 编写方式,您很可能已经习惯或喜欢它。
您可以使用 @pytest.fixture(autouse=True)
标记 fixture 函数,并在您希望使用它的上下文中定义该 fixture 函数。让我们看一个 initdir
fixture,它使 TestCase
类的所有测试方法在一个预先初始化了 samplefile.ini
的临时目录中执行。我们的 initdir
fixture 本身使用 pytest 内置的 tmp_path
fixture 来委托创建每个测试的临时目录
# 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
fixture 函数将用于定义它的类的所有方法。这相当于在类上使用 @pytest.mark.usefixtures("initdir")
标记,就像前面的例子一样。
运行此测试模块 …
$ pytest -q test_unittest_cleandir.py
. [100%]
1 passed in 0.12s
… 得到一个通过的测试,因为 initdir
fixture 函数在 test_method
之前执行了。
注意
unittest.TestCase
方法不能直接接收 fixture 参数,因为实现这一点可能会影响运行一般 unittest.TestCase 测试套件的能力。
上述 usefixtures
和 autouse
示例应该有助于将 pytest fixture 混入 unittest 套件中。
您还可以逐步从继承 unittest.TestCase
转向使用普通断言,然后逐步开始受益于完整的 pytest 功能集。
注意
由于两个框架之间的架构差异,基于 unittest
的测试的 setup 和 teardown 是在测试的 call
阶段执行的,而不是在 pytest
的标准 setup
和 teardown
阶段执行的。在某些情况下,尤其是在分析错误时,理解这一点可能很重要。例如,如果一个基于 unittest
的套件在 setup 期间出现错误,pytest
在其 setup
阶段不会报告任何错误,而是在 call
期间抛出错误。