2014-10-08 36 views
9

想象一下,我已經在模塊foo中實現了一個名爲Bar的實用程序(也許是一個類),併爲它編寫了以下測試。pytest:對於同一接口的不同實現的可重用測試

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> 

現在想象一下,在未來我希望寫在同一接口的不同實現。我想這些實現能夠重用是爲上面的測試套件編寫了測試:需要更改的唯一的東西是

  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) 

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 

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)) 

任何人(庫的甚至是客戶端)撰寫的另一種實現方式此接口

  • 可以重複使用的一組中define_tests.py
  • 注入自己的測試數據定義測試到測試
  • 沒有修改任何原始文件

回答

3

這是一個偉大的使用情況parametrized test fixtures的。

你的代碼可能看起來是這樣的:

from foo import Bar, Baz 

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

def test_one(Implementation): 
    assert Implementation().frobnicate() 

這將有test_one運行兩次:一次是在那裏執行=酒吧和曾經在那裏執行=巴茲。

請注意,由於實現只是一個固定裝置,您可以更改它的範圍,或者做更多的設置(可能實例化類,也許以某種方式配置它)。

如果與pytest.mark.parametrize裝飾器一起使用,pytest將生成所有排列。例如,假設上面的代碼,並且該代碼在這裏:

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

test_two將運行四次,具有以下配置:

  • 實施=酒吧,事= 1
  • 實施=酒吧,事= 2
  • 實施=巴茲,事= 1個
  • 實施=巴茲,事= 2
+0

這將如何讓新實現的作者添加的測試新的參數化而不接觸包含測試定義的原始文件? – jacg 2014-10-16 15:10:58

+0

您可以將燈具定義移動到中心位置,即更高級別的conftest.py文件。文檔在這裏:http://pytest.org/latest/fixture.html#sharing-a-fixture-across-tests-in-a-module-or-class-session 你想讓新作者不要觸摸特定的某些測試文件或測試套件中的任何內容? – 2014-10-16 20:19:09

+0

應該可以在不修改任何現有代碼的情況下添加擴展。如果我發佈一個帶有一些符合實現和它們的測試的庫,那麼庫的客戶端應該能夠添加新的實現*和它們的測試*,而不用修改庫中隨附的任何文件。這是微不足道的,不是因爲我想通過向它們注入新數據來重用Exsting測試。將夾具移動到與庫一起提供的conftest.py中(除非我錯過了某些東西)仍然需要擴展器來更改庫中附帶的文件。 – jacg 2014-10-16 22:52:01

0

沒有類繼承你不能做到這一點,但你不必使用unittest.TestCase。爲了使它更pytest,你可以使用燈具。

它允許您舉例夾具參數化,或使用其他固定。

我嘗試創建一個簡單的例子。

class SomeTest: 

    @pytest.fixture 
    def implementation(self): 
     return "A" 

    def test_a(self, implementation): 
     assert "A" == implementation 


class OtherTest(SomeTest): 

    @pytest.fixture(params=["B", "C"]) 
    def implementation(self, request): 
     return request.param 


def test_a(self, implementation): 
    """ the "implementation" fixture is not accessible out of class """ 
    assert "A" == implementation 

和第二測試失敗

def test_a(self, implementation): 
>  assert "A" == implementation 
E  assert 'A' == 'B' 
E   - A 
E   + B 

    def test_a(self, implementation): 
>  assert "A" == implementation 
E  assert 'A' == 'C' 
E   - A 
E   + C 

    def test_a(implementation): 
     fixture 'implementation' not found 

別忘了你在定義python_class = *Test pytest.ini

相關問題