Tutoriel Python Django (7)

Ceci est un matériel pour les sessions d'étude. Cette fois, j'expliquerai différentes choses sur Test.

Tutoriel Honke (Dans le Honke, il est expliqué comme Tutoriel 5) https://docs.djangoproject.com/ja/1.9/intro/tutorial05/

Autres tutoriels

Lançons le test

Source → 0b1bcccc1da95d274cd92253fa44af7a84c51404

Cela peut ne pas être très pertinent pour un programme que vous écrivez un peu comme passe-temps, Pour les programmes qui sont remis à neuf petit à petit sur une longue période, écrire un bon test vous rendra heureux.

Parlez de l'objectif du test, de sa nécessité et de son utilité dans le didacticiel original et dans [article Qiita](http://qiita.com/search?q=%E3%83%86%E3%82%B9% Veuillez vérifier avec E3% 83% 88).

En python, unittest est préparé comme une bibliothèque standard. django fournit la classe TestCase et la commande d'exécution qui l'étend.

Le code du test est écrit dans tests.py créé lors de la création de l'application. Dans le tutoriel, j'ai créé une application de sondages avec la commande $ python manage.py startapp polls. À ce moment-là, un fichier appelé tests.py doit être créé avec views.py et models.py.

./manage.py
  ├ tutorial/  #Emplacement des fichiers de paramètres, etc.
  │  ├ ...
  │  └ settings.py
  └ polls/  # ← "manage.py startapp polls"Répertoire des applications créées dans
    ├ models.py
    ├ views.py
    ├ ...
    └ tests.py  #← Cela devrait également être créé automatiquement. J'écrirai un test ici

Lorsque vous ouvrez tests.py, vous devriez voir l'état suivant par défaut.

tests.py


from django.test import TestCase

# Create your tests here.

Si vous ajoutez une méthode commençant par test à la classe qui hérite de TestCase, le django collectera et exécutera automatiquement la méthode. Veuillez noter qu'il ne sera exécuté que si les trois conditions suivantes sont remplies.

--Créez un fichier sous l'application qui commence par test --Créez une classe qui hérite de django.test.TestCase --Démarrez le nom de la méthode avec test

tests.py


from django.test import TestCase


class PollsTest(TestCase):
    def test_hoge(self):
        print('test!')
$ ./manage.py test
Creating test database for alias 'default'...
test!
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Destroying test database for alias 'default'...

Faites-en un test semblable à un test

Le test consiste généralement à vérifier si la valeur de retour d'une fonction ou d'une méthode produit les résultats attendus. TestCase de python fournit des méthodes d'assert pour la comparaison, et si le résultat n'est pas comme prévu, Test échouera. Le tableau en montre quatre types.

Méthode Liste de contrôle
assertEqual(a, b) a == b
assertNotEqual(a, b) a != b
assertTrue(x) bool(x) is True
assertFalse(x) bool(x) is False

Si vous vous souvenez des quatre ci-dessus, vous pouvez couvrir presque tous les cas, mais certaines méthodes d'assertion sont utiles en tant que raccourcis. Vous voudrez peut-être lire le site officiel une fois. http://docs.python.jp/3/library/unittest.html#assert-methods

Par exemple, le code qui teste une exception lors de l'exécution d'une fonction est plus facile (et plus facile à comprendre) à écrire en utilisant ʻassertRaises qu'en utilisant ʻassertTrue.

class PollsTest(TestCase):
    def test_exception(self):
        try:
            func()
        except Exception:
            pass
        else:
            self.assertTrue(False)  #Échoue toujours si aucune exception ne se produit

    def test_exception2(self):
        self.assertRaises(Exception, func)

Comparaison des valeurs

Source → 4301169d6eb1a06e01d93282fbaab0f1fc2c367e

Écrivons un test en utilisant ʻassertEqual et ʻassertNotEqual. Comme indiqué dans le tableau, les deux ci-dessus sont des comparaisons avec «==» et «! =», Donc 1 et True et 0 et False sont identiques. Si vous voulez comparer clairement les types, utilisez ʻassertIs ou ʻassertIsNone. Il est nécessaire de concevoir quelque chose comme ʻassertTrue (bool (1)) . (Il semble que ʻassertIs et ʻassertIsNone` ne font pas partie de la série python2)

polls/tests.py


from django.test import TestCase


class PollsTest(TestCase):
    def test_success(self):
        self.assertEqual(1, 1)
        self.assertEqual(1, True)

    def test_failed(self):
        self.assertNotEqual(0, False)
$ ./manage.py test
Creating test database for alias 'default'...
F.
======================================================================
FAIL: test_failed (polls.tests.PollsTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/shimomura/pbox/work/tutorial/tutorial/polls/tests.py", line 10, in test_failed
    self.assertNotEqual(0, False)
AssertionError: 0 == False

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)
Destroying test database for alias 'default'...

Si le test réussit, «.» S'affiche, et s'il échoue, «F» s'affiche avec où il a échoué. Puisque le nombre de tests = le nombre de méthodes, quel que soit le nombre de fois que vous appelez assert dans la même méthode, le nombre de tests sera compté pour un. Il est plus facile de comprendre quel test a échoué si vous écrivez le test en détail, mais le temps de test augmentera en raison de l'initialisation, etc.

test de modèle

La source → c2c65afcb0d26913f683cb7b64f388925d7896eb

Maintenant que vous savez comment comparer les valeurs, importons réellement le modèle et testons si la méthode fonctionne correctement. Cela dit, il n'y a jusqu'à présent qu'une seule méthode de type méthode dans le modèle Question ...

Étant donné que la cible de test est une méthode du modèle Question, il est nécessaire de créer une instance du modèle Question dans le code de test.

polls/models.py (méthode à tester)



class Question(models.Model):
...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

polls/tests.py (code de test)


from django.test import TestCase
from django.utils import timezone

from .models import Question


class PollsTest(TestCase):
    def test_was_published_recently(self):
        obj = Question(pub_date=timezone.now())
        self.assertTrue(obj.was_published_recently())
$ ./manage.py test
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Destroying test database for alias 'default'...

C'est comme ça.

Ajouter un test

La source → 1062f1b4362ef2bf4e9ea483b5e178e7ca82c0c6

Il est rare que le nombre d'exécutions de test pour une méthode soit un, alors augmentons un peu plus le modèle. À propos, le test consiste à vérifier si la méthode fonctionne comme prévu en premier lieu. Le comportement prévu de was_published_recently est de déterminer si une ** question ** a été publiée récemment. ** Récemment ** est ici dans un délai d'un jour à partir du présent. Il existe plusieurs méthodes de test, mais dans le cas d'une telle méthode, il est courant de donner des valeurs avant et après la condition et de vérifier comment elle est jugée. (Cela s'appelle l'analyse de la valeur limite, le test de la valeur limite, etc.)

Kobito.1aoQFZ.png

Cela ressemble à ceci sur la figure. Ajoutons donc un test pour vérifier cette condition. Dans Honke Tutorial, un test est ajouté comme méthode séparée, mais cette fois, test_was_published_recently est étendu pour le moment. Faire. ① et ②, ③ et ④ devraient être aussi proches que possible, mais cette fois il est temps, donc je vais le desserrer un peu.

polls/tests.py


from datetime import timedelta

from django.test import TestCase
from django.utils import timezone

from .models import Question


class PollsTest(TestCase):
    def test_was_published_recently(self):
        #Un peu plus vieux qu'un jour
        obj = Question(pub_date=timezone.now() - timedelta(days=1, minutes=1))
        self.assertFalse(obj.was_published_recently())

        #Un peu plus récent qu'un jour
        obj = Question(pub_date=timezone.now() - timedelta(days=1) + timedelta(minutes=1))
        self.assertTrue(obj.was_published_recently())

        #Récemment publié
        obj = Question(pub_date=timezone.now() - timedelta(minutes=1))
        self.assertTrue(obj.was_published_recently())

        #Publié bientôt
        obj = Question(pub_date=timezone.now() + timedelta(minutes=1))
        self.assertFalse(obj.was_published_recently())
$ ./manage.py test
Creating test database for alias 'default'...
F
======================================================================
FAIL: test_was_published_recently (polls.tests.PollsTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/shimomura/pbox/work/tutorial/tutorial/polls/tests.py", line 25, in test_was_published_recently
    self.assertFalse(obj.was_published_recently())
AssertionError: True is not false

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
Destroying test database for alias 'default'...

Pour le moment, la méthode was_published_recently n'a pas de jugement futur, donc le test dans (4) échoue. De cette façon, même si vous savez que le test a échoué (= FAIL: test_was_published_recently (polls.tests.PollsTest)) si vous avez écrit les méthodes sans les diviser. La raison de l'échec (1), (2), (3) ou (4) ne peut être déterminée qu'à partir du numéro de ligne. Cependant, puisque vous pouvez joindre un message à ʻassert`, il sera un peu plus facile à comprendre si vous définissez un message.

polls/tests.py (ajouter un message à affirmer)


...
    def test_was_published_recently(self):
        #Un peu plus vieux qu'un jour
        obj = Question(pub_date=timezone.now() - timedelta(days=1, minutes=1))
        self.assertFalse(obj.was_published_recently(), 'Publié il y a 1 jour et 1 minute')

        #Un peu plus récent qu'un jour
        obj = Question(pub_date=timezone.now() - timedelta(days=1) + timedelta(minutes=1))
        self.assertTrue(obj.was_published_recently(), 'Publié en 1 jour et 1 minute')

        #Récemment publié
        obj = Question(pub_date=timezone.now() - timedelta(minutes=1))
        self.assertTrue(obj.was_published_recently(), 'Publié il y a 1 minute')

        #Publié bientôt
        obj = Question(pub_date=timezone.now() + timedelta(minutes=1))
        self.assertFalse(obj.was_published_recently(), 'Publié dans 1 minute')
$ ./manage.py test
Creating test database for alias 'default'...
F
======================================================================
FAIL: test_was_published_recently (polls.tests.PollsTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/shimomura/pbox/work/tutorial/tutorial/polls/tests.py", line 25, in test_was_published_recently
    self.assertFalse(obj.was_published_recently(), 'Publié dans 1 minute')
AssertionError: True is not false :Publié dans 1 minute

----------------------------------------------------------------------

L'ajout d'un message a facilité la compréhension. Cette méthode n'est pas non plus parfaite: par exemple, si le jugement de ① échoue, le test s'arrête là, il n'est donc pas possible de confirmer si ② doit ④ fonctionner normalement. Cependant, on peut dire qu'il n'est pas nécessaire de vérifier (2) à (4) car il est nécessaire de le corriger quel que soit le fonctionnement de l'autre quand même un test échoue ...

Dans tous les cas, le test a révélé qu'il n'y avait pas de jugement futur, alors corrigeons-le immédiatement.

polls/models.py


...
class Question(models.Model):
...
    def was_published_recently(self):
        return timezone.now() >= self.pub_date >= timezone.now() - datetime.timedelta(days=1)
$ ./manage.py test
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Destroying test database for alias 'default'...

Vous avez maintenant modifié la méthode pour qu'elle fonctionne comme prévu.

voir le test

Tutoriel Honke

Dans le test du modèle, nous avons vérifié le fonctionnement de la logique de la méthode définie. Dans le test de vue, lorsque l'utilisateur affiche réellement la page,

Sera vérifié.

django fournit une classe de test Client qui simule l'accès dans un navigateur. Commençons par écrire un test pour l'écran d'index des sondages. Dans TestCase, vous pouvez accéder à la classe Client avec self.client.

Vérifiez le code d'état

Source → 852b862423f9908480e60c5657ee028782530933

La page peut-elle être affichée normalement? Pour vérifier , vérifiez status_code. Je pense que le code de statut que vous voyez souvent lorsque vous touchez django est le suivant. → [Code d'état (wikipedia)](https://ja.wikipedia.org/wiki/HTTP%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82 % B9% E3% 82% B3% E3% 83% BC% E3% 83% 89)

code sens
200 Réussite
400 Erreur de paramètre. Par exemple, si la valeur transmise au formulaire est incorrecte
401 Accès sans connexion à une page nécessitant une authentification
404 Pas de page
500 Erreur interne. Ce sera le cas si le code python est étrange

S'il est dans les années 200, il fonctionne normalement, s'il est dans les années 400, c'est une erreur due à une erreur d'opération côté client, N'oubliez pas que les 500 sont des erreurs dues aux programmes et aux serveurs. Cette fois, nous confirmerons que la page peut être affichée normalement, alors confirmez que response.status_code vaut 200.

polls/tests.py


...
from django.shortcuts import resolve_url
...

class ViewTest(TestCase):
    def test_index(self):
        response = self.client.get(resolve_url('polls:index'))
        self.assertEqual(200, response.status_code)

Cette fois, j'ai utilisé self.client.get car il s'agit d'un affichage de page (accès par la méthode GET). Lorsque vous testez Submit comme Form, utilisez self.client.post car il s'agit d'une méthode POST. Vous devez passer l'url comme premier argument.

Confirmation du contenu

La source → d1ad02228ca2cb2370b323fd98cd27a0cf02d7a9

Si le status_code est ** 200 **, cela signifie que la page est affichée lorsqu'elle est visualisée avec un navigateur pour le moment. Ensuite, vérifions si le contenu affiché est comme prévu.

Dans le didacticiel original, ʻassertContainsest utilisé pour vérifier le contenu du html qui est réellement affiché. Par exempleself.assertContains(response, "No polls are available.")` Si vous écrivez comme, assurez-vous que "Aucun sondage n'est disponible." Est inclus dans le code source html affiché.

La valeur de retour de self.client.get, response, a le contexte utilisé pour rendre le html, donc En vérifiant le contenu de ce contexte, vous pouvez vérifier si la pièce générée dynamiquement a la valeur voulue.

polls/views.py


...
def index(request):
    return render(request, 'polls/index.html', {
        'questions': Question.objects.all(),
    })
...

Ce qui précède est la fonction de vue à tester, mais le dictionnaire du troisième argument de la fonction de rendu ({'questions': Question.objects.all ()}) Le contexte est-il passé au modèle.

Lorsque le test est exécuté, un DB pour le test est généré et le DB est recréé pour chaque méthode de test. Par conséquent, même si vous écrivez un test qui exploite la base de données, il ne sera pas affecté par la base de données de production ou d'autres tests. La fonction d'index dans views.py récupère tout le contenu de l'objet Question, Comme mentionné ci-dessus, le contenu de la base de données doit être vide, alors écrivons un test pour vérifier s'il est réellement vide.

Le contenu du contexte ['questions'] est un QuerySet pour le modèle Question. Le nombre de tables de questions est obtenu en appelant count () depuis QuerySet.

polls/tests.py


...
class ViewTest(TestCase):
    def test_index(self):
        response = self.client.get(resolve_url('polls:index'))
        self.assertEqual(200, response.status_code)
        self.assertEqual(0, response.context['questions'].count())

Si le test réussit, enregistrons les données et vérifions l'évolution du nombre de cas et du contenu.

polls/tests.py


class ViewTest(TestCase):
    def test_index(self):
        response = self.client.get(resolve_url('polls:index'))
        self.assertEqual(200, response.status_code)
        self.assertEqual(0, response.context['questions'].count())

        Question.objects.create(
            question_text='aaa',
            pub_date=timezone.now(),
        )
        response = self.client.get(resolve_url('polls:index'))
        self.assertEqual(1, response.context['questions'].count())

        self.assertEqual('aaa', response.context['questions'].first().question_text)

mock

officiel python

Il n'existe pas de programme de ce type pour le moment, mais selon l'application, il y aura naturellement des programmes qui utilisent des API pour se lier avec le monde extérieur. Pour tester les programmes qui nécessitent une communication avec le monde extérieur, python fournit une bibliothèque fictive. En remplaçant la partie où la communication avec l'extérieur se produit par une simulation, vous pouvez remplacer la valeur de retour de l'API par votre valeur préférée.

Kobito.eoQ3cl.png

Essayons de remplacer la fonction qui effectue la communication API par la fonction factice. Utilisez ʻunittest.mock.patchpour le remplacement. Vous pouvez placer la partie où l'appel est effectué danswith, ou vous pouvez définir decorator` dans la méthode de test elle-même.

from unittest import mock


def dummy_api_func():
    return 'dummy api response'


def api_func():
    return 'api response'


class PollsTest(TestCase):
    def test_mocked_api(self):
        ret = api_func()
        print('ret:', ret)
        with mock.patch('polls.tests.api_func', dummy_api_func):
            ret = api_func()  #←←←←←←←← Cet appel devient factice
            print('mocked_ret:', ret)

    @mock.patch('polls.tests.api_func', dummy_api_func)
    def test_mocked_api_with_decorator(self):
        ret = api_func()  #Si vous utilisez un décorateur, ce sera également factice
        print('decorator:', ret)
$ ./manage.py testCreating test database for alias 'default'...
ret: api response
mocked_ret: dummy api response   #← Appliquer le patch avec avec
.decorator: dummy api response   #← Appliquer le patch par le décorateur

Dans le prochain tutoriel, nous expliquerons l'explication supplémentaire sur Model et le fonctionnement depuis le shell. Vers le prochain tutoriel

Autres tutoriels

Recommended Posts

Tutoriel Python Django (5)
Tutoriel Python Django (2)
Tutoriel Python Django (7)
Tutoriel Python Django (1)
Tutoriel du didacticiel Python Django
Tutoriel Python Django (3)
Tutoriel Python Django (4)
Résumé du didacticiel Python Django
Tutoriel Python
Fiche technique du didacticiel Python Django
Résumé du didacticiel Python
mémo du didacticiel django
Démarrer le didacticiel Django 1
Django 1.11 a démarré avec Python3.6
Tutoriel [Docker] (Python + php)
Framework Web Django Python
Didacticiel sur les associations polymorphes Django
tutoriel simple django oscar
Essayez Debian + Python 3.4 + django1.7 ……
Mémo du didacticiel Python OpenCV
[Tutoriel Python] Structure des données
Note du didacticiel Django Girls
Tutoriel Cloud Run (python)
Python Django CSS reflété
Faites Django avec CodeStar (Python3.6.8, Django2.2.9)
Lancez-vous avec Django! ~ Tutoriel ⑤ ~
Introduction à Python Django (2) Win
[Tutoriel Python] Outil de structure de contrôle
Python
Faites Django avec CodeStar (Python3.8, Django2.1.15)
Python3 + Django ~ Mac ~ avec Apache
Création de liste de tâches [Python Django]
Premiers pas avec Python Django (1)
Django
Premiers pas avec Python Django (4)
Premiers pas avec Python Django (3)
Lancez-vous avec Django! ~ Tutoriel ⑥ ~
Installez Python 3.7 et Django 3.0 (CentOS)
[Python] Tutoriel personnel sur l'arbre de décision
Introduction à Python Django (6)
Premiers pas avec Python Django (5)
Jusqu'à la sortie de Python [Django] de Web service [Tutorial Part 1]
8 commandes fréquemment utilisées dans Python Django
Créer une nouvelle application utiliser python, django
python + django + scikit-learn + mecab (1) avec heroku
python + django + scikit-learn + mecab (2) avec heroku
Résumé du didacticiel Django Girls Première moitié
Trébucher lors du didacticiel django 1.7
Déployer le didacticiel Django sur IIS ①
Installer le framework Python django à l'aide de pip
Introduction à Python Django (2) Édition Mac
[Tutoriel Python] Une introduction facile à Python
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 ~
Tutoriel Django Crispy (Construction d'environnement sur Mac)
python kafka
mise à jour de Django