2010-06-24 14 views
5

Il mio framework di test è attualmente basato su un'utilità test-runner che è derivata da Eclipse pydev python test-corridore. Sto passando ad usare Nose, che ha molte delle caratteristiche del mio test-runner personalizzato ma sembra essere un codice di qualità migliore.È possibile eseguire Nose solo eseguire test che sono sottoclassi di TestCase o TestSuite (come unittest.main())

La mia suite di test include un numero di classi di test astratte che in precedenza non erano mai state eseguite. Lo standard python testrunner (e quello personalizzato) eseguivano solo istanze di unittest.TestCase e unittest.TestSuite.

Ho notato che da quando sono passato a Nose è in esecuzione tutto ciò che inizia con il nome "test" che è fastidioso ... perché la convenzione di denominazione che abbiamo usato per i test-mixin sembra anche una classe di test per Naso. In precedenza questi non venivano mai eseguiti come test perché non erano esempi di TestCase o TestSuite.

Ovviamente potrei rinominare i metodi per escludere la parola "test" dai loro nomi ... ci vorrebbe un po 'perché il framework di test è molto grande e ha molta ereditarietà. D'altra parte sarebbe bello se ci fosse un modo per far sì che Nose vedesse solo TestCases e TestSuites come eseguibili ... e nient'altro.

Questo può essere fatto?

risposta

5

Si potrebbe provare a giocare con l'opzione -m per nosetests. Dalla documentazione:

una classe di test è una classe definita in un modulo test che corrisponde testMatch o è una sottoclasse di unittest.TestCase

-m set che testMatch, in questo modo è possibile disattivare il test qualsiasi cosa inizi con il test .

Un'altra cosa è che è possibile aggiungere __test__ = False alla dichiarazione della classe del caso di test, per contrassegnarlo come "non un test".

+0

Il trucco '__test__ = FALSE non è molto utile nel caso di una classe genitore, questo costringerebbe i bambini classi specificare esplicitamente' __test__ = true' o sarebbe essere ignorato, che è un pericoloso trucco da usare. – Guibod

0

È possibile utilizzare l'argomento --attr di nose per specificare un attributo posizionato da unittest.TestCase. Per esempio, io uso:

nosetests --attr="assertAlmostEqual" 

si potrebbe ottenere ancora più attenti utilizzando e e o matching:

nosetests -A "assertAlmostEqual or addTest" 

Vedi unittest's documentation per una lista completa dei metodi/attributi, e Naso description of the capabilities of the --attr plugin.

2

Se si desidera una classe di test veramente astratta, si può semplicemente ereditare la classe astratta dall'oggetto, quindi ereditare successivamente i test.

Ad esempio:

class AbstractTestcases(object): 
    def test_my_function_does_something(self): 
     self.assertEquals("bar", self.func()) 

e poi usarlo con:

class TestMyFooFunc(AbstractTestcases, unittest.TestCase): 
    def setUp(self): 
     self.func = lambda: "foo" 

Poi nosetests preleverà solo i casi di test in TestMyFooFunc e non quelle in AbstractTestCases.

+0

Questo è un buon modo per evitare problemi, ma il test astratto è quindi illeggibile nel mio IDE. – Guibod

0

un addendum alla risposta 's @nailxx:

È possibile impostare __test__ = False nella classe padre e poi utilizzare un metaclasse (vedi This question con alcune spiegazioni brillanti) per impostare di nuovo a True quando sottoclasse.

(Infine, ho trovato una scusa per usare un metaclasse!)

Anche se __test__ è un attributo doppia sottolineatura, dobbiamo impostare in modo esplicito a True, dal momento che non l'impostazione causerebbe python solo per ricerca l'attributo continua su MRO e lo valuta su False.

Pertanto, è necessario verificare all'istanza della classe se una delle classi padre ha __test__ = False. Se questo è il caso e la definizione della classe corrente non ha impostato lo stesso __test__, aggiungeremo '__test__': True agli attributi dict.

Il codice risultante è simile al seguente:

class TestWhenSubclassedMeta(type): 
    """Metaclass that sets `__test__` back to `True` when subclassed. 

    Usage: 

     >>> class GenericTestCase(TestCase, metaclass=TestWhenSubclassed): 
     ...  __test__ = False 
     ... 
     ...  def test_something(self): 
     ...   self.fail("This test is executed in a subclass, only.") 
     ... 
     ... 
     >>> class SpecificTestCase(GenericTestCase): 
     ...  pass 

    """ 

    def __new__(mcs, name, bases, attrs): 
     ATTR_NAME = '__test__' 
     VALUE_TO_RESET = False 
     RESET_VALUE = True 

     values = [getattr(base, ATTR_NAME) for base in bases 
        if hasattr(base, ATTR_NAME)] 

     # only reset if the first attribute is `VALUE_TO_RESET` 
     try: 
      first_value = values[0] 
     except IndexError: 
      pass 
     else: 
      if first_value == VALUE_TO_RESET and ATTR_NAME not in attrs: 
       attrs[ATTR_NAME] = RESET_VALUE 

     return super().__new__(mcs, name, bases, attrs) 

Si potrebbe estendere questo per alcuni comportamenti più implicito del tipo “se il nome inizia con Abstract, impostare __test__ = False automaticamente”, ma per me sarebbe mantenere l'assegnazione esplicita per chiarezza.


Permettetemi di incollare Unittests semplici per dimostrare il comportamento - e per ricordare che tutti dovrebbero prendere i due minuti per testare la loro codice dopo l'introduzione di una funzione.

from unittest import TestCase 

from .base import TestWhenSubclassedMeta 


class SubclassesTestCase(TestCase): 
    def test_subclass_resetted(self): 
     class Base(metaclass=TestWhenSubclassedMeta): 
      __test__ = False 

     class C(Base): 
      pass 

     self.assertTrue(C.__test__) 
     self.assertIn('__test__', C.__dict__) 

    def test_subclass_not_resetted(self): 
     class Base(metaclass=TestWhenSubclassedMeta): 
      __test__ = True 

     class C(Base): 
      pass 

     self.assertTrue(C.__test__) 
     self.assertNotIn('__test__', C.__dict__) 

    def test_subclass_attr_not_set(self): 
     class Base(metaclass=TestWhenSubclassedMeta): 
      pass 

     class C(Base): 
      pass 

     with self.assertRaises(AttributeError): 
      getattr(C, '__test__') 
0

È inoltre possibile utilizzare l'ereditarietà multipla a livello banco di prova e lasciare che l'eredita dalla classe base solo object. Vedere this thread:

class MyBase(object): 
    def finishsetup(self): 
     self.z=self.x+self.y 

    def test1(self): 
     self.assertEqual(self.z, self.x+self.y) 

    def test2(self): 
     self.assert_(self.x > self.y) 

class RealCase1(MyBase, unittest.TestCase): 
    def setUp(self): 
     self.x=10 
     self.y=5 
     MyBase.finishsetup(self) 

class RealCase2(MyBase, unittest.TestCase): 
    def setUp(self): 
     self.x=42 
     self.y=13 
     MyBase.finishsetup(self) 
Problemi correlati