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/
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'...
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)
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.
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.
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.)
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.
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
.
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, utilisezself.client.post
car il s'agit d'une méthode POST. Vous devez passer l'url comme premier argument.
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 exemple
self.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
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.
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é dans
with, 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
Recommended Posts