Pregunta

Este es un seguimiento de un pregunta anterior mía.

En la pregunta anterior, se exploraron métodos para implementar lo que era esencialmente la misma prueba en toda una familia de funciones, asegurando que la prueba no se detuviera en la primera función que falló.

Mi solución preferida utilizaba una metaclase para insertar dinámicamente las pruebas en un unittest.TestCase. Desafortunadamente, la nariz no capta esto porque la nariz escanea estáticamente los casos de prueba.

¿Cómo consigo nariz para descubrir y ejecutar un TestCase? Consulte aquí para ver un ejemplo de TestCase en cuestión.

¿Fue útil?

Solución

La nariz tiene un "generador de prueba" característica para cosas como esta. Usted escribe una función generadora que produce cada '' caso de prueba '' función que desea que se ejecute, junto con sus argumentos. Siguiendo su ejemplo anterior, esto podría verificar cada una de las funciones en una prueba separada:

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)

Otros consejos

Nose no busca pruebas estáticamente, por lo que puede usar la magia de metaclase para hacer pruebas que Nose encuentra.

La parte difícil es que las técnicas de metaclase estándar no establecen el atributo func_name correctamente, que es lo que Nose busca al verificar si los métodos en su clase son pruebas.

Aquí hay una metaclase simple. Mira a través del func dict y agrega un nuevo método para cada método que encuentra, afirmando que el método que encontró tiene una cadena de documentación. Estos nuevos métodos sintéticos reciben los nombres " 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)

Ahora, creemos una nueva clase que use esta metaclase

class Foo(object):
    __metaclass__ = Meta

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

    def greeter_no_docstring(self):
        pass

En tiempo de ejecución, Foo en realidad se denominará Test_Foo y tendrá greeter , greeter_no_docstring , test_1 y test_2 como sus métodos. Cuando ejecuto nosetests en este archivo, aquí está el resultado:

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

Esta metaclase no es realmente útil como es, pero si en su lugar usa el Meta no como una metaclase adecuada, sino como una metaclase más funcional (es decir, toma una clase como argumento y devuelve una nueva clase, una que ha cambiado de nombre para que nose la encuentre), luego es útil. He utilizado este enfoque para probar automáticamente que las cadenas de documentos se adhieren al estándar Numpy como parte de un conjunto de pruebas de nariz.

Además, he tenido muchos problemas para lograr un cierre adecuado trabajando con new.function, por eso este código usa m (self, func) donde func está hecho para ser un argumento predeterminado. Sería más natural usar un cierre sobre value , pero eso no parece funcionar.

Podría intentar generar las clases de caso de prueba 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 .."}
)

Esto debería crearse cuando nose intenta importar su test_module para que funcione.

La ventaja de este enfoque es que puede crear muchas combinaciones de pruebas dinámicamente.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top