Wie erhalte ich die Nase dynamisch generierten Testfälle zu entdecken?
-
19-08-2019 - |
Frage
Dies ist ein Follow-up zu einem vorherige Frage von mir.
In der vorherige Frage wurden Methoden untersucht, um das umzusetzen, was im Wesentlichen der gleiche Test über eine ganze Familie von Funktionen, Prüfung gewährleistet nicht beim ersten Stopp-Funktion hat, die fehlgeschlagen.
Meine bevorzugte Lösung verwendet, um eine Metaklasse dynamisch die Tests in eine unittest.TestCase einzufügen. Leider statisch nicht diese Nase holen, weil Nase für Testfälle durchsucht.
Wie erhalte ich die Nase zu entdecken und so einen Testcase laufen? Siehe hier für ein Beispiel für den Testfall in Frage.
Lösung
Nase hat eine „Testgenerator“ -Funktion für Sachen wie diese. Sie schreiben eine Generatorfunktion, die jede „Testfall“ Funktion liefert Sie es, zusammen mit seiner args ausgeführt werden sollen. Nach Ihrer vorherigen Beispiel das jede der Funktionen in einem separaten Test überprüfen können:
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)
Andere Tipps
Nose sucht nicht nach Tests statisch, so dass Sie können verwenden metaclass Magie Tests zu machen, dass Nase findet.
Der schwierige Teil ist, dass Standard-Metaklasse Techniken nicht korrekt angeben, die func_name Attribut, das ist, was Nase sucht, wenn geprüft wird, ob Methoden auf Ihrer Klasse sind Tests.
Hier ist ein einfaches metaclass. Es sieht durch die func dict und fügt eine neue Methode für jede Methode es findet, zu behaupten, dass es das Verfahren fand eine docstring hat. Diese neuen Synthesemethoden sind die Namen "test_%d" %i
gegeben.
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)
Lassen Sie uns nun eine neue Klasse erstellen, die diese Metaklasse verwendet
class Foo(object):
__metaclass__ = Meta
def greeter(self):
"sdf"
print 'Hello World'
def greeter_no_docstring(self):
pass
Zur Laufzeit Foo
tatsächlich Test_Foo
benannt werden und wird greeter
, greeter_no_docstring
, test_1
und test_2
als seine Methoden. Wenn ich nosetests
auf diese Datei ausgeführt, hier ist die Ausgabe:
$ 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)
Dieses Metaklasse ist nicht wirklich nützlich wie es ist, aber wenn man stattdessen die Meta
nicht als richtiger metaclass verwenden, sondern eher als eine funktionellen metaclass (dh nimmt eine Klasse als Argument und gibt eine neue Klasse, eine, die umbenannt ist so wird die Nase er) finden, dann ist es ist nützlich. Ich habe diesen Ansatz verwendet, um automatisch zu prüfen, dass die Docstrings zum Numpy Standard als Teil einer Nase Testsuite zu halten.
Außerdem habe ich eine Menge Ärger hatte ordnungsgemäße Schließung immer mit new.function arbeiten, weshalb dieser Code verwendet m(self, func)
wo func
gemacht wird ein Standardargument zu sein. Es wäre natürlich einen Verschluss über value
zu verwenden, aber das scheint nicht zu funktionieren.
Sie könnten versuchen, die Testcase-Klassen mit Typ ()
zu erzeugen,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 .."}
)
Dies sollte erstellt werden, wenn die Nase versucht Ihr test_module zu importieren, damit es funktionieren soll.
Der Vorteil dieses Ansatzes ist, dass Sie dynamisch viele Kombinationen von Tests erstellen können.