测试类和方法

已完成

使用 Pytest 除了能编写测试函数外,还能使用类。 正如我们已经提到的,不需要继承,测试类只遵循一些简单的规则。 使用类可以提高灵活性和可重用性。 正如你接下来会看到的,Pytest 不会妨碍你,并会避免强制你以某种方式编写测试。

与函数一样,仍然可以使用 assert 语句编写断言。

生成测试类

让我们使用一个实际场景来了解测试类如何提供帮助。 以下函数检查给定文件在其内容中是否包含“yes”。 如果包含,则返回 True。 如果文件不存在,或在其内容中包含“no”,则返回 False。 这种情况在使用文件系统指示完成情况的异步任务中很常见。

该函数如下所示:

import os

def is_done(path):
    if not os.path.exists(path):
        return False
    with open(path) as _f:
        contents = _f.read()
    if "yes" in contents.lower():
        return True
    elif "no" in contents.lower():
        return False

下面是名为 test_files.py 的文件中的具有两个测试(每个条件一个测试)的类:

class TestIsDone:

    def test_yes(self):
        with open("/tmp/test_file", "w") as _f:
            _f.write("yes")
        assert is_done("/tmp/test_file") is True

    def test_no(self):
        with open("/tmp/test_file", "w") as _f:
            _f.write("no")
        assert is_done("/tmp/test_file") is False

注意

测试方法对临时测试文件使用 /tmp 路径,因为它更容易用于示例。 但是,如果需要使用临时文件,请考虑使用 tempfile 之类的库,它可安全地创建(和删除)这些文件。 并非所有系统都有 /tmp 目录,并且该位置可能不是临时的,具体取决于操作系统。

若使用 -v 标志运行测试以增加详细程度,可显示测试通过:

pytest -v test_files.py
============================= test session starts ==============================
Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 
cachedir: .pytest_cache
rootdir: /private/
collected 2 items

test_files.py::TestIsDone::test_yes PASSED                               [ 50%]
test_files.py::TestIsDone::test_no PASSED                                [100%]

============================== 2 passed in 0.00s ===============================

虽然通过了测试,但它们看起来存在重复,并且在测试完成后还会留下文件。 让我们先在下一部分中了解帮助程序方法,然后再了解如何改进测试。

帮助器方法

在测试类中,可以使用几种方法来设置和拆解测试执行。 如果定义了测试,Pytest 会自动执行。 若要使用这些方法,应知道它们具有特定的顺序和行为。

  • setup:在类中的每个测试之前执行一次
  • teardown:在类中的每个测试之后执行一次
  • setup_class:在类中的所有测试之前执行一次
  • teardown_class:在类中的所有测试之后执行一次

如果测试需要类似的(或相同的)资源才能正常工作时,编写设置方法会很有用。 理想情况下,测试完成后不应留下资源,因此在这些情况下,拆解方法可以帮助进行测试清理。

清理

现在来看一个更新的测试类,它会在每次测试后清理文件:

class TestIsDone:

    def teardown(self):
        if os.path.exists("/tmp/test_file"):
            os.remove("/tmp/test_file")

    def test_yes(self):
        with open("/tmp/test_file", "w") as _f:
            _f.write("yes")
        assert is_done("/tmp/test_file") is True

    def test_no(self):
        with open("/tmp/test_file", "w") as _f:
            _f.write("no")
        assert is_done("/tmp/test_file") is False

由于使用了 teardown() 方法,所以此测试类不再遗留 /tmp/test_file

设置

我们可以对此类执行的另一个改进是添加指向文件的变量。 由于该文件现已在六个位置声明,因此对路径的任何更改都意味着在所有这些位置更改它。 此示例展示添加了声明路径变量的 setup() 方法后该类是什么样子:

class TestIsDone:

    def setup(self):
        self.tmp_file = "/tmp/test_file"

    def teardown(self):
        if os.path.exists(self.tmp_file):
            os.remove(self.tmp_file)

    def test_yes(self):
        with open(self.tmp_file, "w") as _f:
            _f.write("yes")
        assert is_done(self.tmp_file) is True

    def test_no(self):
        with open(self.tmp_file, "w") as _f:
            _f.write("no")
        assert is_done(self.tmp_file) is False

自定义帮助程序方法

可以在类中创建自定义帮助程序方法。 这些方法不得以名称 test 为前缀,并且不能命名为安装程序或清理方法。 在 TestIsDone 类中,我们可以在自定义帮助程序中自动创建临时文件。 该自定义帮助程序方法可能如以下示例所示:

    def write_tmp_file(self, content):
        with open(self.tmp_file, "w") as _f:
            _f.write(content)

Pytest 不会自动执行 write_tmp_file() 方法,其他方法可以直接调用该方法以保存重复的任务,例如写入文件。

将测试方法更新为使用自定义帮助程序后,整个类如下例所示:

class TestIsDone:

    def setup(self):
        self.tmp_file = "/tmp/test_file"

    def teardown(self):
        if os.path.exists(self.tmp_file):
            os.remove(self.tmp_file)

    def write_tmp_file(self, content):
        with open(self.tmp_file, "w") as _f:
            _f.write(content)

    def test_yes(self):
        self.write_tmp_file("yes")
        assert is_done(self.tmp_file) is True

    def test_no(self):
        self.write_tmp_file("no")
        assert is_done(self.tmp_file) is False

何时使用类而不是函数

关于何时使用类而不是函数没有任何严格的规则。 一个好的做法是持续遵循项目和团队中一直使用的惯例做法。 可通过下面这些常用问题来帮助确定何时使用类:

  • 测试是否需要类似的设置或清理帮助程序代码?
  • 将测试组合在一起是否有逻辑上的意义?
  • 测试套件中是否至少有一些测试?
  • 测试能否受益于常用的帮助程序函数?