[PYTHON] Créons une fonction pour le test paramétré à l'aide d'un objet frame

Créons une fonction pour le test paramétré à l'aide d'un objet frame

(Cet article n'a pas été inclus dans le calendrier de l'avent Python. Il était complet.)

Qu'est-ce que le test paramétré?

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.

Décorateur de fonction

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.

fonction de test unittest

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.

Pile d'appels

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.

La variable locale dans le cadre un cadre au-dessus du décorateur de méthode est la position de la classe

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.

finalement

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

Créons une fonction pour le test paramétré à l'aide d'un objet frame
Créons un environnement virtuel pour Python
Comment créer un objet fonction à partir d'une chaîne
Créons une API REST en utilisant SpringBoot + MongoDB
Faisons un module pour Python en utilisant SWIG
Créer une fonction en Python
Essayez de dessiner une fonction logistique
Créons une fonction pour maintenir Button dans Tkinter
Créer une interface graphique python à l'aide de tkinter
Créer un dictionnaire imbriqué à l'aide de defaultdict
Créer une API CRUD à l'aide de l'API rapide
Créez un wrapper de langage C à l'aide de Boost.Python
Comment diviser et traiter une trame de données à l'aide de la fonction groupby
Comment faire un modèle pour la détection d'objets avec YOLO en 3 heures
Utilisez Matplotlib pour créer plusieurs graphiques linéaires à partir d'un bloc de données à la fois
Créez un modèle de prédiction de survie pour les passagers du Kaggle Titanic sans utiliser Python
Créons une fonction de chat avec Vue.js + AWS Lambda + dynamo DB [Paramètres AWS]
Un mémorandum sur l'utilisation de la fonction d'entrée de Python
Créez un modèle pour votre planning Django
Créons un groupe gratuit avec Python
Créer un graphique à l'aide du module Sympy
Impressions d'utilisation de Flask pendant un mois
[Python] Créer un environnement Batch à l'aide d'AWS-CDK
Créez de la documentation et testez du code en utilisant doctest.testfile ()
Faisons un site multilingue en utilisant flask-babel
Créer un dictionnaire Hatena pour SKK (supplémentaire)
Equipé d'une fonction carte utilisant payjp
Créer un bloc de données à partir d'Excel à l'aide de pandas
Faisons un plug-in backend pour Errbot
[AWS] Lançons un test unitaire de la fonction Lambda dans l'environnement local
Écrivons un processus d'agrégation pour une certaine période en utilisant pandas × groupby × Grouper