(Cet article n'a pas été inclus dans le calendrier de l'avent Python. Il était complet.)
C'est un test exécuté avec des paramètres spécifiés. Je veux faire quelque chose comme ce qui suit.
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):
"""Comparaison simple des ajouts"""
result = self._callFUT(x, y)
self.assertEqual(result, expected)
Le résultat de l'exécution ressemblera à ce qui suit. _test_add est appelé 5 fois avec des paramètres différents.
...F.
======================================================================
FAIL: test_add_G4 (__main__.Tests)
Comparaison simple des ajouts: 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)
Il s'agit de créer une telle fonction paramétrée.
Un décorateur de fonction est une fonction qui encapsule une fonction. Vous pouvez modifier le comportement de la fonction en l'enveloppant. Il s'agit essentiellement d'une fonction qui renvoie une fonction.
Lorsque la classe Hello suivante a été définie.
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
Définissez un double décorateur comme celui ci-dessous.
def double(method):
def _double(self): #Puisque c'est une méthode, prends toi-même
result = method(self)
return result + result
return _double
double est un décorateur qui produit deux fois le résultat de la méthode interne. Si vous attachez ceci à la méthode hello de la classe Hello plus tôt, le comportement de Hello.hello () changera.
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 reconnaît les méthodes commençant par "test_" comme méthodes de test. Par conséquent, pour le convertir en un test paramétré, il est nécessaire de générer plusieurs méthodes de test.
Essayons de créer un décorateur de classe et augmentons le nombre de méthodes de test. Le décorateur de classe prend la classe et retourne la classe. C'est une version de classe du décorateur de fonctions.
import unittest
def add_foo_test(names):
def _add_foo_test(cls):
for name in names:
def test(self):
#Faites un test qui réussit dans le texte
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 est une fonction qui crée une méthode de test qui réussit à envoyer des SMS en utilisant une liste de noms passés. Cette fois, "foo", "bar", "boo" ont été réussis, donc les méthodes de test test_foo, test_bar, test_boo ont été créées.
Le résultat est le suivant.
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
Vous faites trois tests. Dois-je faire quelque chose de similaire avec un test paramétré? Cela signifie que.
frame object
Mais il y a encore des problèmes. La fonction paramétrée que je voulais créer était une fonction pour décorer une méthode. Cependant, ce que je faisais à l'endroit précédent était un décorateur qui décore la classe. Cela ne fonctionnera pas tel quel.
Il semble que vous deviez définir la valeur de la classe à partir de la méthode. C'est quelque chose comme récupérer une pile d'appels en cours d'exécution.
La pile d'appels est la pile sur laquelle l'environnement est chargé lors de l'exécution de la fonction. Il tient l'environnement au moment de chaque appel. (Pour cette raison, même si vous l'appelez récursivement, cela fonctionnera.)
Jetons un œil à un exemple. Il existe des fonctions f et g. C'est comme g appelle f. f est une fonction qui renvoie la variable locale à ce moment-là sous forme de dictionnaire.
def f(x):
return locals() #Extrayez les variables locales de cet environnement sous forme de dictionnaire.
def g(x):
r = f(x*x)
print(locals())
return r
print(g(10))
# {'r': {'x': 100}, 'x': 10} # g
# {'x': 100} # f
Lorsque je l'exécute, seul x est renvoyé.
Ajoutons une variable locale de g dans f. Au moment de l'appel, il a la forme suivante, vous pouvez donc voir g de f en accédant à la pile d'appels.
[f] <-- current
[g]
[toplevel]
La façon de le toucher est d'utiliser une fonction suspecte appelée sys._getframe. En argument, nous passerons le nombre d'images que vous souhaitez à partir de la position actuelle.
[f] <- sys._getframe()Entrer
[g] <- sys._getframe(1)Entrer
[toplevel]
Ajoutons une variable appelée dummy. Modifiez le code comme suit.
import sys
def f(x):
sys._getframe(1).f_locals["dummy"] = "*dummy*" #Ajouter un mannequin à la variable locale de l'image suivante ci-dessus
return locals() #Extrayez les variables locales de cet environnement sous forme de dictionnaire.
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
De cette façon, j'ai pu changer la variable locale en g en f.
Quel était l'intérêt de toucher la pile d'appels précédente? C'est parce que la variable locale dans le cadre un cadre au-dessus du décorateur de méthode est la position de la classe. En d'autres termes, en regardant dans la pile d'appels dans la fonction paramétrée que vous essayiez de créer, vous pouvez accéder à la classe qui a cette méthode depuis la fonction en tant que décorateur de méthode. Laisse moi te donner un exemple.
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
Le décorateur appelé add_A est un décorateur de méthode. Vous pouvez accéder à la classe via la pile d'appels. Une variable de classe appelée A est définie.
Résumons les résultats jusqu'à présent.
Vous pouvez maintenant créer une fonction paramétrée en les combinant. Voici la mise en œuvre.
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
Vous l'avez fait, pas vous.
Recommended Posts