(Dieser Artikel hat es nicht in den Python-Adventskalender geschafft. Er war voll.)
Es ist ein Test, der mit angegebenen Parametern ausgeführt wird. Ich möchte so etwas wie das Folgende tun.
def add(x, y):
return x + y
class Tests(unittest.TestCase):
def _callFUT(self, x, y):
return add(x, y)
@paramaterized([
(1, 2, 3),
(1, 1, 2),
(1, 0, 1),
(2, 3, 4),
(4, 4, 8)
])
def _test_add(self, x, y, expected):
"""Einfacher Additionsvergleich"""
result = self._callFUT(x, y)
self.assertEqual(result, expected)
Das Ausführungsergebnis sieht wie folgt aus. _test_add wird 5 mal mit verschiedenen Parametern aufgerufen.
...F.
======================================================================
FAIL: test_add_G4 (__main__.Tests)
Einfacher Additionsvergleich: args=(2, 3), expected=4
----------------------------------------------------------------------
Traceback (most recent call last):
File "test-paramaterize.py", line 24, in test
return method(self, *candidates[n])
File "test-paramaterize.py", line 52, in _test_add
self.assertEqual(result, expected)
AssertionError: 5 != 4
----------------------------------------------------------------------
Ran 5 tests in 0.001s
FAILED (failures=1)
Es geht darum, wie man eine solche parametrisierte Funktion erstellt.
Ein Funktionsdekorateur ist eine Funktion, die eine Funktion umschließt. Sie können das Verhalten der Funktion ändern, indem Sie sie einschließen. Es ist im Grunde eine Funktion, die eine Funktion zurückgibt.
Wenn die folgende Hello-Klasse definiert wurde.
class Hello(object):
def __init__(self, name):
self.name = name
def hello(self):
return "hello {name}".format(name=self.name)
Hello("foo").hello() # hello foo
Definieren Sie einen Doppeldekorateur wie den folgenden.
def double(method):
def _double(self): #Da es eine Methode ist, nimm dich selbst
result = method(self)
return result + result
return _double
double ist ein Dekorateur, der das Ergebnis der internen Methode zweimal ausgibt. Wenn Sie dies früher an die Hallo-Methode der Hello-Klasse anhängen, ändert sich das Verhalten von Hello.hello ().
class Hello(object):
def __init__(self, name):
self.name = name
@double
def hello(self):
return "hello {name}".format(name=self.name)
print(Hello("foo").hello()) # hello foo.hello foo.
unitttest.TestCase erkennt Methoden, die mit "test_" beginnen, als Testmethoden. Um es in einen parametrisierten Test umzuwandeln, müssen daher mehrere Testmethoden generiert werden.
Versuchen wir, einen Klassendekorator zu erstellen und die Anzahl der Testmethoden zu erhöhen. Der Klassendekorateur nimmt die Klasse und gibt sie zurück. Es ist eine Klassenversion des Funktionsdekorators.
import unittest
def add_foo_test(names):
def _add_foo_test(cls):
for name in names:
def test(self):
#Machen Sie einen Test, der im Text erfolgreich ist
self.assertEqual(1, 1)
test.__name__ = "test_{name}".format(name=name)
setattr(cls, test.__name__, test)
return cls
return _add_foo_test
@add_foo_test(["foo", "bar", "boo"])
class Tests(unittest.TestCase):
pass
unittest.main()
add_foo_test ist eine Funktion, die eine Testmethode erstellt, mit der eine SMS mit einer Liste übergebener Namen erfolgreich gesendet werden kann. Dieses Mal wurden "foo", "bar", "boo" übergeben, sodass die Testmethoden test_foo, test_bar, test_boo erstellt wurden.
Das Ergebnis ist wie folgt.
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
Sie machen drei Tests. Sollte ich mit einem parametrisierten Test etwas Ähnliches machen? Es bedeutet das.
frame object
Aber es gibt immer noch Probleme. Die parametrisierte Funktion, die ich erstellen wollte, war eine Funktion zum Dekorieren einer Methode. Was ich jedoch an der vorherigen Stelle gemacht habe, war ein Dekorateur, der die Klasse dekoriert. Es wird nicht so funktionieren wie es ist.
Es sieht so aus, als müssten Sie den Wert der Klasse über die Methode festlegen. Dies ist so etwas wie das Abrufen eines laufenden Aufrufstapels.
Der Aufrufstapel ist der Stapel, auf den die Umgebung während der Ausführung der Funktion geladen wird. Es enthält die Umgebung zum Zeitpunkt jedes Anrufs. (Aus diesem Grund funktioniert es auch dann, wenn Sie es rekursiv aufrufen.)
Schauen wir uns ein Beispiel an. Es gibt Funktionen f und g. Es ist wie g ruft f. f ist eine Funktion, die die lokale Variable zu diesem Zeitpunkt als Wörterbuch zurückgibt.
def f(x):
return locals() #Extrahieren Sie lokale Variablen in dieser Umgebung als Wörterbuch.
def g(x):
r = f(x*x)
print(locals())
return r
print(g(10))
# {'r': {'x': 100}, 'x': 10} # g
# {'x': 100} # f
Wenn ich es ausführe, wird nur x zurückgegeben.
Fügen wir eine lokale Variable von g in f hinzu. Zum Zeitpunkt des Aufrufs hat es die folgende Form, sodass Sie g von f aus sehen können, indem Sie auf den Aufrufstapel zugreifen.
[f] <-- current
[g]
[toplevel]
Sie können es berühren, indem Sie eine verdächtige Funktion namens sys._getframe verwenden. Als Argument übergeben wir die Anzahl der gewünschten Frames von der aktuellen Position.
[f] <- sys._getframe()Reinkommen
[g] <- sys._getframe(1)Reinkommen
[toplevel]
Fügen wir eine Variable namens Dummy hinzu. Ändern Sie den Code wie folgt.
import sys
def f(x):
sys._getframe(1).f_locals["dummy"] = "*dummy*" #Fügen Sie der lokalen Variablen des obigen Frames einen Dummy hinzu
return locals() #Extrahieren Sie lokale Variablen in dieser Umgebung als Wörterbuch.
def g(x):
r = f(x*x)
print(locals())
return r
print(g(10)) # {'x': 100}
# {'r': {'x': 100}, 'x': 10, 'dummy': '*dummy*'} # g
# {'x': 100} # f
Auf diese Weise konnte ich die lokale Variable in g in f ändern.
Was war der Grund, den vorherigen Aufrufstapel zu berühren? Dies liegt daran, dass die lokale Variable im Frame ein Frame über dem Methodendekorator die Position der Klasse ist. Mit anderen Worten, indem Sie in den Aufrufstapel der parametrisierten Funktion schauen, die Sie erstellen wollten, können Sie als Methodendekorator innerhalb der Funktion auf die Klasse zugreifen, die diese Methode enthält. Lassen Sie mich Ihnen ein Beispiel geben.
import sys
def add_A(method):
sys._getframe(1).f_locals["A"] = "AAAAAA"
return method
class Boo(object):
@add_A
def hello(self):
pass
print(Boo.A) # AAAAAA
Der Dekorator mit dem Namen add_A ist ein Methodendekorator. Sie können über den Aufrufstapel auf die Klasse zugreifen. Eine Klassenvariable namens A wird gesetzt.
Fassen wir die bisherigen Ergebnisse zusammen.
Sie können jetzt eine parametrisierte Funktion erstellen, indem Sie diese kombinieren. Das Folgende ist die Implementierung.
import sys
i = 0
def gensym():
global i
i += 1
return "G{}".format(i)
def paramaterized(candidates):
"""candidates = [(args, ..., expected)]"""
def _parameterize(method):
env = sys._getframe(1).f_locals
method_name = method.__name__
for i, args in enumerate(candidates):
paramaters = args[:-1]
expected = args[-1]
def test(self, n=i):
return method(self, *candidates[n])
test.__name__ = "{}_{}".format(method_name.lstrip("_"), gensym())
doc = method.__doc__ or method_name
test.__doc__ = "{}: args={}, expected={}".format(doc, paramaters, expected)
env[test.__name__] = test
return method
return _parameterize
Du hast es getan, nicht wahr?