pytest:对同一接口的不同实现的可重用测试

jac*_*acg 13 python unit-testing pytest

想象一下,我已经实现了一个Bar在模块中调用的实用程序(可能是一个类)foo,并为它编写了以下测试.

test_foo.py:

from foo import Bar as Implementation
from pytest import mark

@mark.parametrize(<args>, <test data set 1>)
def test_one(<args>):
    <do something with Implementation and args>

@mark.parametrize(<args>, <test data set 2>)
def test_two(<args>):
    <do something else with Implementation and args>

<more such tests>
Run Code Online (Sandbox Code Playgroud)

现在想象一下,在未来我希望能够编写相同接口的不同实现.我希望这些实现能够重用为上述测试套件编写的测试:唯一需要改变的是

  1. 进口的 Implementation
  2. <test data set 1>,<test data set 2>等等.

所以我正在寻找一种以可重用的方式编写上述测试的方法,这将允许接口的新实现的作者能够通过将实现和测试数据注入其中来使用测试,而无需修改包含测试原始规范的文件.

在pytest中这样做的好方法是什么呢?

================================================== ==================

================================================== ==================

这是一个单元测试版本(不漂亮但是)有效.

define_tests.py:

# Single, reusable definition of tests for the interface. Authors of
# new implementations of the interface merely have to provide the test
# data, as class attributes of a class which inherits
# unittest.TestCase AND this class.
class TheTests():

    def test_foo(self):
        # Faking pytest.mark.parametrize by looping
        for args, in_, out in self.test_foo_data:
            self.assertEqual(self.Implementation(*args).foo(in_),
                             out)

    def test_bar(self):
        # Faking pytest.mark.parametrize by looping
        for args, in_, out in self.test_bar_data:
            self.assertEqual(self.Implementation(*args).bar(in_),
                             out)
Run Code Online (Sandbox Code Playgroud)

v1.py:

# One implementation of the interface
class Implementation:

    def __init__(self, a,b):
        self.n = a+b

    def foo(self, n):
        return self.n + n

    def bar(self, n):
        return self.n - n
Run Code Online (Sandbox Code Playgroud)

v1_test.py:

# Test for one implementation of the interface
from v1 import Implementation
from define_tests import TheTests
from unittest import TestCase

# Hook into testing framework by inheriting unittest.TestCase and reuse
# the tests which *each and every* implementation of the interface must
# pass, by inheritance from define_tests.TheTests
class FooTests(TestCase, TheTests):

    Implementation = Implementation

    test_foo_data = (((1,2), 3,  6),
                     ((4,5), 6, 15))

    test_bar_data = (((1,2), 3,  0),
                     ((4,5), 6,  3))
Run Code Online (Sandbox Code Playgroud)

编写该接口的另一个实现的任何人(甚至是库的客户端)

  • 可以重用在中定义的测试集 define_tests.py
  • 将自己的测试数据注入测试中
  • 不修改任何原始文件

Fra*_*k T 6

这是参数化测试治具的绝佳用例。

您的代码可能看起来像这样:

from foo import Bar, Baz

@pytest.fixture(params=[Bar, Baz])
def Implementation(request):
    return request.param

def test_one(Implementation):
    assert Implementation().frobnicate()
Run Code Online (Sandbox Code Playgroud)

这将test_one运行两次:一次在Implementation = Bar位置,一次在Implementation = Baz位置。

请注意,由于Implementation只是一个固定装置,因此您可以更改其范围,或进行更多设置(可能实例化该类,或者以某种方式对其进行配置)。

如果与pytest.mark.parametrize装饰器一起使用,则pytest将生成所有排列。例如,假设上面的代码,并且此代码在这里:

@pytest.mark.parametrize('thing', [1, 2])
def test_two(Implementation, thing):
    assert Implementation(thing).foo == thing
Run Code Online (Sandbox Code Playgroud)

test_two 将使用以下配置运行四次:

  • 实现=酒吧,事情= 1
  • 实现=栏,东西= 2
  • 实现=巴兹,事情= 1
  • 实现= Baz,事物= 2

  • 应该可以在不修改*任何*现有代码的情况下添加扩展。如果我发布了一个带有一些符合要求的实现及其测试的库,库的客户应该能够添加新的实现 * 和他们的测试 *,而无需修改库附带的 *任何 * 文件。如果不是因为我想通过向其中注入新数据来重用现有测试,这将是微不足道的。将夹具移动到库随附的 conftest.py 中(除非我遗漏了某些内容)仍然需要扩展程序更改库随附的文件。 (2认同)