[PYTHON] Tutoriel Django (Créer une application de blog) ④ --Test d'unité

La dernière fois, Django Tutorial (Create Blog App) ③ --Article List Display est une vue générale basée sur les classes pour afficher une liste d'articles créés à partir du site de gestion. J'ai utilisé.

J'aimerais ajouter le traitement CRUD tel que la création d'article, les détails, l'édition et la suppression dans l'application telle quelle, mais retenons et incluons ** test unitaire **.

À propos des tests Django

C'est amusant d'ajouter plus de fonctionnalités, mais écrivez-vous habituellement des tests?

Même ceux qui sont devenus capables de créer des applications Django simples grâce à divers tutoriels, etc. Je pense que cela peut provoquer une erreur lorsque vous jouez un peu avec. De plus, même si aucune erreur n'est générée lorsque Django est démarré avec runserver, etc. Vous pouvez remarquer une erreur lorsque vous déplacez réellement l'écran via le navigateur.

Bien sûr, vous pouvez tester manuellement certaines opérations, mais il est inutile de le faire à chaque fois.

Par conséquent, il est recommandé d'effectuer un test unitaire à l'aide de la fonction Django. Django vous permet d'automatiser les tests en utilisant la classe UnitTest, donc Une fois que vous n'avez écrit que le code de test en premier, vous n'avez pas à refaire la même chose encore et encore.

Penser aux tests est aussi important que penser au code de développement, Il existe même une méthode de développement consistant à créer un test puis à écrire le code pour le fonctionnement de l'application.

Maintenant que vous avez la possibilité de tester, économisez votre temps de test et travaillez dur pour améliorer l'application elle-même.

À propos de la structure des dossiers

À ce stade, la structure des dossiers doit être la suivante.

.
├── blog
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py #Attention
│   ├── urls.py
│   └── views.py
├── db.sqlite3
├── manage.py
├── mysite
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── templates
    └── blog
        ├── index.html
        └── post_list.html

Comme vous l'avez peut-être remarqué, un fichier appelé ** tests.py ** est automatiquement créé sous le répertoire du blog.

Vous pouvez créer des cas de test directement dans ce tests.py, Il est plus facile de gérer si les fichiers sont séparés pour chaque test de modèle, test de vue et test. Créez un répertoire de tests comme indiqué ci-dessous et créez un fichier vide dans chacun d'eux. Le but est de créer un fichier ** __ init__.py ** à partir du contenu afin que les fichiers du répertoire tests soient également exécutés.

.
├── blog
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   └── __init__.py
│   ├── models.py
│   ├── tests #ajouter à
│   │   ├── __init__.py
│   │   ├── test_models.py
│   │   ├── test_urls.py
│   │   └── test_views.py
......

Veuillez noter que Django ne reconnaîtra pas le nom du module à moins qu'il ne commence par "test".

Comment rédiger un test

Django étend la classe TestCase standard Python (unittest.TestCase), Utilisez la propre classe TestCase de Django (django.test.TestCase). Dans cette classe, vous pouvez utiliser une méthode appelée assertion, qui a une fonction pour vérifier si la valeur de retour est la valeur attendue.

De plus, comme mentionné ci-dessus, le module de test doit commencer par la chaîne "test". La méthode de test doit également commencer par la chaîne "test" (nous en parlerons plus tard).

En suivant cette règle, Django pourra trouver la méthode de test dans votre projet et l'exécuter automatiquement.

test_models.py Commençons par tester le modèle. Pour récapituler, le modèle de publication décrit dans blog / models.py ressemble à ceci.

models.py


...

class Post(models.Model):
    title = models.CharField('Titre', max_length=200)
    text = models.TextField('Texte')
    date = models.DateTimeField('Date', default=timezone.now)

    def __str__(self): #Définit la valeur à renvoyer lorsque le modèle Post est appelé directement
        return self.title #Renvoie le titre de l'article

Testons ce modèle avec les trois cas suivants cette fois.

  1. Rien n'est enregistré dans l'état initial
  2. Si vous créez correctement un enregistrement, un seul enregistrement sera compté.
  3. Enregistrez les données avec le contenu spécifié et lorsque vous les retirez immédiatement, la même valeur que lorsque vous les avez enregistrées est renvoyée.

Commençons par le premier.

Ouvrez test_models.py et déclarez les modules requis.

test_models.py


from django.test import TestCase
from blog.models import Post

Ensuite, nous allons créer une classe de test, mais assurez-vous d'en faire une classe qui hérite de TestCase.

test_models.py


from django.test import TestCase
from blog.models import Post

class PostModelTests(TestCase):

Maintenant, écrivons une méthode de test dans cette classe PostModelTest. En commençant par "test" dans une classe qui hérite de TestCase Django reconnaîtra automatiquement qu'il s'agit d'une méthode de test. Par conséquent, assurez-vous de nommer la méthode commençant par test après def.

test_models.py


from django.test import TestCase
from blog.models import Post

class PostModelTests(TestCase):

  def test_is_empty(self):
      """Vérifiez que rien n'est enregistré dans l'état initial"""  
      saved_posts = Post.objects.all()
      self.assertEqual(saved_posts.count(), 0)

Stocker le modèle Post actuel dans saved_posts Nous avons confirmé que le nombre de comptage (nombre d'articles) est "0" avec assertEqual.

Vous êtes maintenant prêt à faire un test. Exécutons-le une fois avec ça.

Pour exécuter le test, exécutez la commande suivante dans le répertoire (dans mysite) où se trouve manage.py. Lorsque vous l'exécutez, Django trouvera et exécutera une méthode de test qui suit la convention de dénomination.

(blog) bash-3.2$ python3 manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.....
----------------------------------------------------------------------
Ran 1 tests in 0.009s

OK

Cela signifie que vous avez exécuté un test et l'avez terminé sans aucune erreur.

En passant, j'ai confirmé que les données sont vides (= 0) dans Post plus tôt, mais attendons-nous à ce qu'il y ait une donnée.

test_models.py(temporaire)


from django.test import TestCase
from blog.models import Post

class PostModelTests(TestCase):

  def test_is_empty(self):
      """État initial, mais il s'agit de vérifier si des données existent(une erreur est attendue)"""  
      saved_posts = Post.objects.all()
      self.assertEqual(saved_posts.count(), 1)

Le résultat de l'exécution du test à ce moment est le suivant.

(blog) bash-3.2$ python3 manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_is_empty (blog.tests.test_models.PostModelTests)
État initial, mais il s'agit de vérifier si des données existent(une erreur est attendue)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/masuyama/workspace/MyPython/MyDjango/blog/mysite/blog/tests/test_models.py", line 9, in test_is_empty
    self.assertEqual(saved_posts.count(), 1)
AssertionError: 0 != 1

----------------------------------------------------------------------
Ran 1 test in 0.002s

FAILED (failures=1)

Vous obtenez une AssertionError et le test échoue car ce n'est pas ce à quoi vous vous attendiez (c'est une expérience réussie).

Dans le test de Django, vous pouvez également enregistrer temporairement des données dans la base de données à partir de la méthode create, donc Vous pouvez également exécuter le reste des tests que vous ne pourriez pas voir sans enregistrer les données. Vous trouverez ci-dessous comment rédiger un test de modèle, veuillez donc vous y référer.

test_models.py(Texte intégral)


from django.test import TestCase
from blog.models import Post

class PostModelTests(TestCase):

  def test_is_empty(self):
    """Vérifiez que rien n'est enregistré dans l'état initial"""  
    saved_posts = Post.objects.all()
    self.assertEqual(saved_posts.count(), 0)
  
  def test_is_count_one(self):
    """Vérifiez que si vous créez correctement un enregistrement, un seul enregistrement sera compté"""
    post = Post(title='test_title', text='test_text')
    post.save()
    saved_posts = Post.objects.all()
    self.assertEqual(saved_posts.count(), 1)

  def test_saving_and_retrieving_post(self):
    """Enregistrez les données avec le contenu spécifié et vérifiez que lorsque vous les récupérez immédiatement, la même valeur que lorsque vous les avez enregistrées est renvoyée."""
    post = Post()
    title = 'test_title_to_retrieve'
    text = 'test_text_to_retrieve'
    post.title = title
    post.text = text
    post.save()

    saved_posts = Post.objects.all()
    actual_post = saved_posts[0]

    self.assertEqual(actual_post.title, title)
    self.assertEqual(actual_post.text, text)

test_urls.py Outre le modèle, vous pouvez également vérifier si le routage que vous avez écrit dans urls.py fonctionne. Avec le recul, blog / urls.py ressemblait à ceci.

blog/urls.py


from django.urls import path
from . import views

app_name = 'blog'

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('list', views.PostListView.as_view(), name='list'),
]

Dans le routage ci-dessus, le routage est défini en fonction de l'adresse saisie sous / blog /, donc / blog / Teste lorsque les éléments suivants sont '' (vide) et 'list'. Utilisez assertEqual pour comparer et vérifier si les résultats redirigés vers chaque page via la vue sont attendus.

test_urls.py


from django.test import TestCase
from django.urls import reverse, resolve
from ..views import IndexView, PostListView

class TestUrls(TestCase):

  """Redirection testée lors de l'accès par URL à la page d'index"""
  def test_post_index_url(self):
    view = resolve('/blog/')
    self.assertEqual(view.func.view_class, IndexView)

  """Tester la redirection vers la page de liste d'articles"""
  def test_post_list_url(self):
    view = resolve('/blog/list')
    self.assertEqual(view.func.view_class, PostListView)

Maintenant, exécutons le test une fois.

(blog) bash-3.2$ python3 manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.....
----------------------------------------------------------------------
Ran 5 tests in 0.007s

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

test_views.py Enfin, testons la vue.

views.py ressemble à ceci.

views.py


from django.views import generic
from .models import Post  #Importer le modèle de poste

class IndexView(generic.TemplateView):
    template_name = 'blog/index.html'

class PostListView(generic.ListView): #Hériter de la classe générique ListView
    model = Post #Appelez le modèle que vous souhaitez lister

Le test IndexView vérifie que le code d'état 200 (= succès) est renvoyé lors de l'accès par la méthode GET.

test_views.py


from django.test import TestCase
from django.urls import reverse

from ..models import Post

class IndexTests(TestCase):
  """Classe de test IndexView"""

  def test_get(self):
    """Confirmez que le code d'état 200 est renvoyé en accédant avec la méthode GET."""
    response = self.client.get(reverse('blog:index'))
    self.assertEqual(response.status_code, 200)

Lorsque vous ajoutez une méthode dans n'importe quelle vue, Peu importe le temps dont vous disposez pour rédiger un test, prenez l'habitude de le créer comme un cas de test minimum.

Nous allons également tester le ListView.

Sans parler de la confirmation que le code d'état 200 est également renvoyé. Ici, après avoir ajouté deux données (articles), la liste d'articles s'affiche et Créez un test pour vous assurer que chacun des titres d'articles enregistrés est inclus dans la liste.

Notez que nous utiliserons ici une méthode légèrement spéciale. J'ai mentionné plus tôt que la méthode de test commence par "test", mais il existe des méthodes ** setUp ** et ** tearDown **.

Dans la méthode setUp, enregistrez les données utilisées dans le cas de test et La méthode tearDown vous permet de supprimer les données enregistrées dans la méthode setUp. (Notez que les deux doivent indiquer explicitement les données à enregistrer)

L'écriture d'un processus qui enregistre des données plusieurs fois dans le même cas de test est un facteur qui prend du temps et du temps pour les tests. Le processus courant consiste à les rassembler en un seul endroit.

Si vous utilisez ces méthodes et créez test_views.py, cela ressemblera à ceci.

test_views.py


from django.test import TestCase
from django.urls import reverse

from ..models import Post

class IndexTests(TestCase):
  """Classe de test IndexView"""

  def test_get(self):
    """Confirmez que le code d'état 200 est renvoyé en accédant avec la méthode GET."""
    response = self.client.get(reverse('blog:index'))
    self.assertEqual(response.status_code, 200)

class PostListTests(TestCase):

  def setUp(self):
    """
Une méthode pour préparer l'environnement de test. Assurez-vous de le nommer "setUp".
S'il y a des données que vous souhaitez utiliser en commun dans la même classe de test, créez-les ici.
    """
    post1 = Post.objects.create(title='title1', text='text1')
    post2 = Post.objects.create(title='title2', text='text2')

  def test_get(self):
    """Confirmez que le code d'état 200 est renvoyé en accédant avec la méthode GET."""
    response = self.client.get(reverse('blog:list'))
    self.assertEqual(response.status_code, 200)
  
  def test_get_2posts_by_list(self):
    """Confirmez que 2 éléments supplémentaires ajoutés par la méthode setUp sont retournés lors de l'accès avec GET"""
    response = self.client.get(reverse('blog:list'))
    self.assertEqual(response.status_code, 200)
    self.assertQuerysetEqual(
      #Dans le modèle Post__str__Puisqu'il est configuré pour renvoyer le titre à la suite de, vérifiez si le titre renvoyé est comme publié
      response.context['post_list'],
      ['<Post: title1>', '<Post: title2>'],
      ordered = False #Spécifiez pour ignorer la commande
    )
    self.assertContains(response, 'title1') #Assurez-vous que le titre post1 est inclus dans le html
    self.assertContains(response, 'title2') #Assurez-vous que le titre post2 est inclus dans le html

  def tearDown(self):
      """
Une méthode de nettoyage qui efface les données ajoutées par setUp.
Bien qu'il soit "create", en définissant le nom de la méthode sur "tearDown", le traitement inverse de setUp est effectué = il est supprimé.
      """
      post1 = Post.objects.create(title='title1', text='text1')
      post2 = Post.objects.create(title='title2', text='text2')

Si vous exécutez le test dans cet état, 8 tests au total seront exécutés sur le modèle, l'URL et la vue.

(blog) bash-3.2$ python3 manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
........
----------------------------------------------------------------------
Ran 8 tests in 0.183s

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

Vous avez maintenant créé un test unitaire pour le code que vous avez écrit jusqu'à présent. Si d'autres modèles attendus sont appelés, etc. Il existe également un moyen de vérifier de manière redondante avec un test en utilisant la propre méthode de test de Django. Prenez l'habitude d'écrire des tests avant d'écrire du code et évitez de vérifier plus tard.

La prochaine fois, je pourrai créer des articles dans l'application. Allons-y avec le style TDD (Test Driven Development) d'écrire le test unitaire que nous avons appris cette fois en premier.

Recommended Posts

Tutoriel Django (Créer une application de blog) ④ --Test d'unité
Tutoriel Django (Créer une application de blog) ⑤ --Fonction de création d'article
Tutoriel Django (Créer une application de blog) ① --Préparation, Création de la première page
Tutoriel Django (Créer une application de blog) ③ --Affichage de la liste d'articles
Tutoriel Django (Créer une application de blog) ⑦ --Front End Complete
Tutoriel Django (Créer une application de blog) ⑥ - Détails de l'article / Fonctions d'édition / suppression
Tutoriel Django (création d'applications de blog) ② - création de modèles, préparation de site de gestion
Résumé du tutoriel Django pour les débutants par les débutants ⑤ (test)
Tutoriel Python Django (5)
Tutoriel Python Django (2)
création de table django
test unitaire numpy
mémo du didacticiel django
Résumé du tutoriel Django pour les débutants par les débutants ① (création de projet ~)
Démarrer le didacticiel Django 1
Tutoriel Python Django (1)
Tutoriel du didacticiel Python Django
Tutoriel Python Django (3)
Tutoriel Python Django (4)
Qu'est-ce qu'un chien? Volume de démarrage de la création de l'application Django --startapp
Qu'est-ce qu'un chien? Volume de démarrage de la création de l'application Django - startproject
[Django] Ajout d'une nouvelle fonction de création de questions à l'application de sondages
Écrire du code dans UnitTest une application Web Python
Résumé du didacticiel Python Django
Lancer mon application Django
Didacticiel sur les associations polymorphes Django
Initialiser l'application Django
tutoriel simple django oscar
Fonction de création de décalage Django
Note du didacticiel Django Girls
Effectuer un test unitaire de Databricks Notebook
modèle de test unitaire python