[PYTHON] Tutoriel pour faire du développement piloté par les tests (TDD) avec Flask-2 Decorators

introduction

Au cours des 5 fois (il est prévu et sujet à changement), nous vous enseignerons le savoir-faire nécessaire pour un développement piloté par les tests avec Flask. Dans ce deuxième article, je vais vous montrer comment utiliser un décorateur pour compresser la quantité de code dans votre code de test.

1er Tutoriel pour faire du développement piloté par les tests (TDD) avec l'édition client de test Flask-1 2e article 3e écriture 4ème écriture 5ème écriture

Public cible

Aperçu

Code de test pour les API écrites dans Flask avec plusieurs points de terminaison, La quantité de code est compressée à l'aide d'un décorateur.

Structure du répertoire

Placez l'exemple de code utilisé dans cet article dans la structure de répertoires suivante.

flask_02/
├── Dockerfile
└── app
    ├── flask_app.py
    └── test
        ├── decorators.py
        ├── test1.py
        └── test2.py

version docker

$ docker --version
Docker version 19.03.12, build 48a66213fe

Préparation du code

Dockerfile

Dockerfile


FROM python:3.6
USER root

RUN apt update
RUN /usr/local/bin/python -m pip install --upgrade pip
RUN pip install flask==1.1.2

COPY ./app /root/

WORKDIR /root/test

flask_app.py (cible de test)

Code pour une application Flask avec 3 points de terminaison.

flask_app.py


from flask import Flask
app = Flask(__name__)

@app.route('/hello_world')
def hello_world():
    return 'Hello, World!'

@app.route('/good_morning')
def good_morning():
    return 'Good, Morning!'

@app.route('/good_night')
def good_night():
    return 'Good, Night!'

if __name__ == '__main__':
    app.run(host="0.0.0.0",port=5000)

test1.py (code de test)

Pour flask_app.py Code de test qui implémente trois cas de test.

test1.py


import sys
sys.path.append('../')
import flask_app
import unittest

class Test_flask_app_Système normal(unittest.TestCase):

    def setUp(self):
        self.ENDPOINT    = "http://localhost:5000/{}"
        self.DATA        = None
        self.STATUS      = "200 OK"
        self.STATUS_CODE = 200
        self.ROUTE       = None
        
    def test_1_hello_Pouvoir accéder au monde(self):
        # 1.Définir des variables spécifiques au cas de test
        self.DATA  = b"Hello, World!"
        self.ROUTE = "hello_world"
        
        # 2.Parties communes des cas de test
        with flask_app.app.test_client() as client:
            response = client.get(self.ENDPOINT.format(self.ROUTE))
        assert response.data        == self.DATA
        assert response.status      == self.STATUS 
        assert response.status_code == self.STATUS_CODE

        return

    def test_2_good_Accès au matin(self):
        # 1.Définir des variables spécifiques au cas de test
        self.DATA  = b"Good, Morning!"
        self.ROUTE = "good_morning"
       
        # 2.Parties communes des cas de test
        with flask_app.app.test_client() as client:
            response = client.get(self.ENDPOINT.format(self.ROUTE))
        assert response.data        == self.DATA
        assert response.status      == self.STATUS 
        assert response.status_code == self.STATUS_CODE

        return

    def test_3_good_Accès à la nuit(self):
        # 1.Définir des variables spécifiques au cas de test
        self.DATA  = b"Good, Night!"
        self.ROUTE = "good_night"

        # 2.Parties communes des cas de test
        with flask_app.app.test_client() as client:
            response = client.get(self.ENDPOINT.format(self.ROUTE))
        
        assert response.data        == self.DATA
        assert response.status      == self.STATUS 
        assert response.status_code == self.STATUS_CODE

        return

if __name__ == '__main__':
    unittest.main()

test2.py (code de test)

Pour flask_app.py Code de test qui implémente trois cas de test. Le contenu du test en cours est le même que test1.py, Ce code utilise un décorateur.

test2.py


import unittest
# 1.Chargez le décorateur
from decorators import *

#Le nom de la classe fonctionne en japonais
class Test_flask_app_Système normal(unittest.TestCase):

    def setUp(self):
        self.ENDPOINT    = "http://localhost:5000/{}"
        self.DATA        = None
        self.STATUS      = "200 OK"
        self.STATUS_CODE = 200
        self.ROUTE       = None

    # 2.Modifier le décorateur
    @get_test()
    def test_1_hello_Pouvoir accéder au monde(self):    
        # 3.Définir des variables spécifiques au cas de test
        self.DATA  = b"Hello, World!"
        self.ROUTE = "hello_world"
        return

    # 2.Modifier le décorateur
    @get_test()
    def test_2_good_Accès au matin(self):
        # 3.Définir des variables spécifiques au cas de test
        self.DATA  = b"Good, Morning!"
        self.ROUTE = "good_morning"
        return

    # 2.Modifier le décorateur
    @get_test()
    def test_3_good_Accès à la nuit(self):
        # 3.Définir des variables spécifiques au cas de test
        self.DATA  = b"Good, Night!"
        self.ROUTE = "good_night"
        return

if __name__ == '__main__':
    unittest.main()

decorators.py

Décorateur utilisé dans test2.py.

decorators.py


import sys
sys.path.append('../')
import flask_app

#Définition de décorateur
def get_test():
    #Recevoir une fonction pour tester
    def recv_func(test_func):
        #Décorez la fonction de test reçue
        def wrapper(self):
            # 1.Cas de test d'appel
            test_func(self)
            # 2.Agrégation de processus communs
            with flask_app.app.test_client() as client:
                response = client.get(self.ENDPOINT.format(self.ROUTE))
            assert response.data        == self.DATA
            assert response.status      == self.STATUS 
            assert response.status_code == self.STATUS_CODE    
        return wrapper
    return recv_func

Lancer le test

Vérifiez [Structure du répertoire](# structure du répertoire) et exécutez la commande suivante.

$ ls
Dockerfile      app
$ docker build -t pytest .
~réduction~
$ docker run -it pytest /usr/local/bin/python /root/test/test1.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.006s

OK
$ docker run -it pytest /usr/local/bin/python /root/test/test2.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.007s

OK

Explication de test1.py (code de test)

1. Définir des variables spécifiques au cas de test

En 1., les variables utilisées dans chaque cas de test sont initialisées. Cette fois, il stocke les valeurs de retour de point de terminaison et d'API. Le point de terminaison et la valeur de retour de l'API sont différents car le point de terminaison et la valeur de retour de l'API à tester sont différents. L'initialisation est effectuée avec des valeurs différentes pour chaque cas de test.

2. Parties communes des cas de test

En 2., à l'aide du client de test, exécutez l'API de flask_app.py et stockez la valeur de retour en réponse. Les instructions d'assertion suivantes sont utilisées pour comparer l'état, le code d'état et la valeur de retour stockés dans la réponse. Le même processus est effectué dans les trois cas de test.

Inconvénients de l'écriture de test1.py

①. Augmentation du coût du développement basé sur les tests

Plus précisément, parce que les parties communes à chaque cas de test expliqué en 2. ne sont pas agrégées. Même s'il s'agit d'une correction mineure, il est nécessaire de la corriger dans tous les cas de test. Par conséquent, il est possible que la modification du code de test prenne du temps lors du développement piloté par les tests.

②. Le coût de maintenance du code de test augmente

Par exemple, si pour une raison quelconque la personne qui a développé ce code de test est partie Le successeur a une grande quantité de code de test, qui peut prendre du temps à déchiffrer. De plus, dans cet exemple test1.py, il n'y a que trois cas de test, Le code de test commercial est susceptible d'avoir de nombreux cas de test. Par conséquent, cette méthode d'écriture peut augmenter le coût de maintenance.

Explication de decorators.py (décorateur) et test2.py (code de test)

Pour résoudre les inconvénients de test1.py Agrégez le code de test à l'aide d'un décorateur.

Explication de decorators.py

1. Appel du cas de test

Un cas de test décoré avec la fonction imbriquée de niveau supérieur (get_test ()), Il peut être exécuté avec test_func (self). De plus, puisque l'argument self est le même que le cas de test cible self, La propriété définie par setUp (self) peut être héritée.

2. Agrégation des processus communs

Le processus écrit dans le wrapper est Il peut être utilisé dans des cas de test modifiés par un décorateur. Autrement dit, en écrivant le processus couramment utilisé dans wrapper (self), Il est possible de compresser la quantité de code. Cette fois, le processus de comparaison de l'état, du code d'état et de la valeur de retour à l'aide du client de test et de l'instruction assert, qui sont couramment utilisés dans le cas de test, peut être utilisé en commun.

Explication de test2.py (code de test)

1. Chargez le décorateur

Chargez toutes les fonctions de décorateur définies dans decorators.py.

2. Modifier le décorateur

Modifiez le cas de test avec get_test () défini dans decorators.py.

3. Définir des variables spécifiques au scénario de test

Les valeurs de retour du point de terminaison et de l'API sont différentes pour chaque scénario de test, donc Il ne peut pas être agrégé dans un décorateur. Par conséquent, il est initialisé dans le cas de test.

Avantages de l'utilisation de décorateurs dans le code de test

Il est possible de réduire les inconvénients décrits dans [Inconvénients de l'écriture de test1.py](## Inconvénients de l'écriture de test1.py).

Résumé

Nous avons introduit une méthode pour réduire la quantité de code dans le code de test en utilisant un décorateur pour le code de test. Vous pouvez l'utiliser avec d'autres applications Flask simplement en réécrivant le contenu de wrapper (self) dans test2.py.

la prochaine fois

J'écrirai un article sur Fukahori de la méthode get du client de test et du test système anormal.

Recommended Posts

Tutoriel pour faire du développement piloté par les tests (TDD) avec Flask-2 Decorators
Tutoriel pour faire du développement piloté par les tests (TDD) avec Flask-1 Test Client Edition
[Test Driven Development (TDD)] Chapitre 21 Résumé
Code de test pour évaluer les décorateurs
Historique d'apprentissage pour participer au développement d'applications d'équipe avec Python ~ Tutoriel Django 5 ~
Historique d'apprentissage pour participer au développement d'applications d'équipe avec Python ~ Tutoriel Django 4 ~
Historique d'apprentissage pour participer au développement d'applications d'équipe avec Python ~ Tutoriel Django 1, 2, 3 ~
Historique d'apprentissage pour participer au développement d'applications d'équipe avec Python ~ Tutoriel Django 6 ~
Historique d'apprentissage pour participer au développement d'applications d'équipe en Python ~ Tutoriel Django 7 ~
Mettre en œuvre un test piloté par table en Java
Développement piloté par les tests avec Django Partie 4
Développement piloté par les tests avec Django Partie 6
Développement piloté par les tests avec Django Partie 2
Développement piloté par les tests avec Django Partie 1
Développement piloté par les tests avec Django Partie 5
Découvrez la partie I «Monnaie multinationale» du livre «Test Driven Development» avec Python
Démarrage du développement piloté par les tests avec PySide & Pytest
(Pour moi) Mettez Flask dans VS Code
Conseils pour créer de grandes applications avec Flask
Résumé du tutoriel Django pour les débutants par les débutants ⑤ (test)