2014-10-08 13 views
9

Immaginate di aver implementato un programma di utilità (forse una classe) chiamato Bar in un modulo foo, e ho scritto i seguenti test per questo.pytest: test riutilizzabili per diverse implementazioni della stessa interfaccia

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> 

Ora immaginate che, in futuro, mi aspetto diverse implementazioni della stessa interfaccia da scrivere. Vorrei quelle implementazioni per essere in grado di riutilizzare i test che sono stati scritti per la suite di test di cui sopra: le uniche cose che hanno bisogno di cambiare sono

  1. L'importazione del Implementation
  2. <test data set 1>, <test data set 2> ecc

Quindi sto cercando un modo per scrivere i test di cui sopra in modo riutilizzabile, che consentirebbe agli autori di nuove implementazioni dell'interfaccia di essere in grado di utilizzare i test iniettando l'implementazione e i dati di test in essi, senza dover modificare il file contenente le specifiche originali dei test.

Quale sarebbe un buon modo idiomatico di farlo in pytest?

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

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

Qui è una versione unittest che (non è carina ma) funziona.

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

Chiunque (anche un cliente della biblioteca) scrivendo un'altra implementazione di questa interfaccia

  • può riutilizzare il set di test definiti define_tests.py
  • iniettare propri dati di test nelle prove
  • senza modificare qualsiasi file originali

risposta

3

Questo è un caso d'uso per parametrized test fixtures.

Il vostro codice potrebbe essere simile a questa:

from foo import Bar, Baz 

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

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

questo sarebbe due volte test_one run: una volta in cui l'attuazione = bar e una volta in cui l'attuazione = Baz.

Si noti che dal momento che l'implementazione è solo una fixture, è possibile modificarne l'ambito o eseguire altre impostazioni (magari creare un'istanza della classe, magari configurarla in qualche modo).

Se utilizzato con il decoratore pytest.mark.parametrize, pytest genererà tutte le permutazioni. Ad esempio, supponendo che il codice precedente, e questo codice qui:

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

test_two verrà eseguito quattro volte, con le seguenti configurazioni:

  • Attuazione = Bar, cosa = 1
  • Attuazione = Bar , cosa = 2
  • Attuazione = Baz, cosa = 1
  • Attuazione = Baz, cosa = 2
+0

Come questo consentirà di nuove implementazioni gli autori di aggiungere nuove parametrizzazioni dei test senza toccare i file originali contenenti le definizioni del test? – jacg

+0

È possibile spostare la definizione del dispositivo in un punto centrale, un file conftest.py a un livello superiore. La documentazione è qui: http://pytest.org/latest/fixture.html#sharing-a-fixture-across-tests-in-a-module-or-class-session Vuoi mantenere nuovi autori da toccare alcuni file di test in particolare o qualcosa nella suite di test? –

+0

Dovrebbe essere possibile aggiungere estensioni senza modificare * qualsiasi * codice esistente. Se spedisco una libreria con alcune implementazioni conformi e i relativi test, i client della libreria dovrebbero essere in grado di aggiungere nuove implementazioni * e i loro test * senza modificare * qualsiasi * dei file forniti con la libreria. Questo sarebbe banale, se non fosse per il fatto che voglio riutilizzare i test exsting inserendo nuovi dati in essi. Spostando l'apparecchio in un conftest.py che viene fornito con la libreria, si richiederebbe (a meno che non mi sia sfuggito qualcosa) l'estensione per modificare un file fornito con la libreria. – jacg

0

Non è possibile farlo senza ereditarietà delle classi, ma non è necessario utilizzare unittest.TestCase. Per renderlo più pytest puoi usare le fixture.

Consente ad esempio di parametrizzare il proiettore o di utilizzare altri dispositivi di fissaggio.

Provo a creare un semplice esempio.

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 

e seconda prova fallisce

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 

Non dimenticare è necessario definire python_class = *Test in pytest.ini

Problemi correlati