Comment puis-je avoir le nez pour découvrir des cas de test générés dynamiquement?

StackOverflow https://stackoverflow.com/questions/347574

  •  19-08-2019
  •  | 
  •  

Question

Ceci est un suivi de question précédente du mien.

Dans la question précédente, nous avons exploré des méthodes pour mettre en œuvre ce qui était essentiellement le même test sur toute une famille de fonctions, en veillant à ce que les tests ne se limitent pas à la première fonction ayant échoué.

Ma solution préférée utilisait une métaclasse pour insérer dynamiquement les tests dans un unittest.TestCase. Malheureusement, le nez ne le détecte pas, car le nez recherche statistiquement les cas de test.

Comment puis-je avoir le nez pour découvrir et exécuter un tel TestCase? Veuillez consulter ici pour un exemple de TestCase en question.

Était-ce utile?

La solution

Le nez a un "générateur de test" fonctionnalité pour des choses comme ça. Vous écrivez une fonction génératrice qui génère chaque " test case " fonction que vous souhaitez exécuter, ainsi que ses arguments. Suivant votre exemple précédent, cela pourrait vérifier chacune des fonctions dans un test séparé:

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)

Autres conseils

Nose ne recherche pas les tests de manière statique. vous pouvez donc utiliser la magie de la métaclasse pour effectuer des tests que Nose trouve.

La difficulté réside dans le fait que les techniques de métaclasse standard ne définissent pas correctement l'attribut func_name, ce que recherche Nose lorsqu'il vérifie si les méthodes de votre classe sont des tests.

Voici une métaclasse simple. Il examine le func dict et ajoute une nouvelle méthode pour chaque méthode trouvée, affirmant que la méthode trouvée contient une chaîne de documentation. Ces nouvelles méthodes synthétiques se voient attribuer le nom "quot_ %%". % 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)

Maintenant, créons une nouvelle classe qui utilise cette métaclasse

class Foo(object):
    __metaclass__ = Meta

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

    def greeter_no_docstring(self):
        pass

À l'exécution, Foo sera nommé Test_Foo et aura greeter , greeter_no_docstring , test_1 et test_2 en tant que méthodes. Quand je lance nosetests sur ce fichier, voici le résultat:

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

Cette métaclasse n'est pas vraiment utile en l'état, mais si vous utilisez plutôt le Meta , non pas comme métaclasse appropriée, mais plutôt comme une métaclasse fonctionnelle (c'est-à-dire prend une classe comme argument et retourne une nouvelle classe, renommée pour que le nez la trouve), puis elle est utile . J'ai utilisé cette approche pour vérifier automatiquement que les chaînes de documentation adhèrent à la norme Numpy dans le cadre d'une suite de tests nasaux.

De plus, j'ai eu beaucoup de difficulté à obtenir une fermeture correcte avec new.function. C'est pourquoi ce code utilise m (self, func) func est fait pour être un argument par défaut. Il serait plus naturel d’utiliser une fermeture sur valeur , mais cela ne semble pas fonctionner.

Vous pouvez essayer de générer les classes de testcase avec 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 .."}
)

Ceci devrait être créé lorsque nose essaie d'importer votre test_module pour que cela fonctionne.

L'avantage de cette approche est que vous pouvez créer de nombreuses combinaisons de tests de manière dynamique.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top