Domanda

Questo è il seguito di un domanda precedente mia.

Nella domanda precedente, sono stati esplorati metodi per implementare quello che era essenzialmente lo stesso test su un'intera famiglia di funzioni, assicurando che i test non si fermassero alla prima funzione fallita.

La mia soluzione preferita ha usato una metaclasse per inserire dinamicamente i test in unittest.TestCase. Sfortunatamente, nose non lo rileva perché il naso esegue la scansione statica dei casi di test.

Come ottengo il naso per scoprire ed eseguire un TestCase? Fare riferimento qui per un esempio del TestCase in questione.

È stato utile?

Soluzione

Il naso ha un "generatore di test" caratteristica per cose come questa. Scrivi una funzione del generatore che produce ogni "caso di test" funzione che vuoi che funzioni, insieme ai suoi argomenti. Seguendo il tuo esempio precedente, questo potrebbe controllare ciascuna delle funzioni in un test separato:

import unittest
import numpy

from somewhere import the_functions

def test_matrix_functions():
    for function in the_functions:
        yield check_matrix_function, function

def check_matrix_function(function)
    matrix1 = numpy.ones((5,10))
    matrix2 = numpy.identity(5)
    output = function(matrix1, matrix2)
    assert matrix1.shape == output.shape, \
           "%s produces output of the wrong shape" % str(function)

Altri suggerimenti

Nose non esegue la scansione statica dei test, quindi puoi usare la magia della metaclasse per fare i test che Nose trova.

La parte difficile è che le tecniche di metaclasse standard non impostano correttamente l'attributo func_name, che è ciò che Nose cerca quando controlla se i metodi della tua classe sono test.

Ecco una semplice metaclasse. Esamina il funzionamento e aggiunge un nuovo metodo per ogni metodo che trova, affermando che il metodo trovato ha una dotstring. A questi nuovi metodi sintetici vengono dati i nomi "test_% d" % I .

import new
from inspect import isfunction, getdoc

class Meta(type):
    def __new__(cls, name, bases, dct):

        newdct = dct.copy()
        for i, (k, v) in enumerate(filter(lambda e: isfunction(e[1]), dct.items())):
            def m(self, func):
                assert getdoc(func) is not None

            fname = 'test_%d' % i
            newdct[fname] = new.function(m.func_code, globals(), fname,
                (v,), m.func_closure)

        return super(Meta, cls).__new__(cls, 'Test_'+name, bases, newdct)

Ora creiamo una nuova classe che utilizza questa metaclasse

class Foo(object):
    __metaclass__ = Meta

    def greeter(self):
        "sdf"
        print 'Hello World'

    def greeter_no_docstring(self):
        pass

In fase di esecuzione, Foo sarà effettivamente chiamato Test_Foo e avrà greeter , greeter_no_docstring , test_1 e test_2 come metodi. Quando eseguo nosetests su questo file, ecco l'output:

$ nosetests -v test.py
test.Test_Foo.test_0 ... FAIL
test.Test_Foo.test_1 ... ok

======================================================================
FAIL: test.Test_Foo.test_0
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Library/Frameworks/EPD64.framework/Versions/7.3/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
    self.test(*self.arg)
  File "/Users/rmcgibbo/Desktop/test.py", line 10, in m
    assert getdoc(func) is not None
AssertionError

----------------------------------------------------------------------
Ran 2 tests in 0.002s

FAILED (failures=1)

Questa metaclasse non è davvero utile così com'è, ma se invece usi il Meta non come una metaclasse corretta, ma come più di una metaclasse funzionale (ovvero accetta una classe come argomento e restituisce una nuova classe, quella che è stata rinominata in modo che nose possa trovarla), quindi è utile. Ho usato questo approccio per testare automaticamente che i docstring aderiscono allo standard Numpy come parte di una suite di test del naso.

Inoltre, ho avuto molti problemi a far funzionare correttamente la chiusura con new.function, motivo per cui questo codice utilizza m (self, func) dove func è fatto per essere un argomento predefinito. Sarebbe più naturale usare una chiusura su value , ma ciò non sembra funzionare.

Potresti provare a generare le classi testcase con type ()

class UnderTest_MixIn(object):

    def f1(self, i):
        return i + 1

    def f2(self, i):
        return i + 2

SomeDynamicTestcase = type(
    "SomeDynamicTestcase", 
    (UnderTest_MixIn, unittest.TestCase), 
    {"even_more_dynamic":"attributes .."}
)

# or even:

name = 'SomeDynamicTestcase'
globals()[name] = type(
    name, 
    (UnderTest_MixIn, unittest.TestCase), 
    {"even_more_dynamic":"attributes .."}
)

Questo dovrebbe essere creato quando nose tenta di importare il tuo test_module, quindi dovrebbe funzionare.

Il vantaggio di questo approccio è che puoi creare molte combinazioni di test in modo dinamico.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top