• 10 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 4/25/22

Implémentez vos tests pour framework Django avec pytest-django

Pour commencer, vous devez installer le plugin pytest-django pour tester un projet utilisant le framework Django. Utilisez la commande ci-dessous :

pip install pytest-django

Créez un projet Django

Nous allons maintenant devoir créer un petit projet Django avant de voir comment implémenter des tests unitaires sur un tel projet. Ne vous inquiétez pas, je vais vous indiquer toutes les commandes nécessaires pour créer ce projet. N’oubliez pas de créer avant tout un environnement virtuel !

Si ce n'est pas déjà fait, installons Django et les modules de test :

pip install django pytest-django

Nous pouvons maintenant créer le projet Django et une application que nous appelleronslibrary:

django-admin startproject django_pytest
cd django_pytest/
python manage.py startapp library

Ajoutez ensuite la nouvelle application dans le fichiersettings.py:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'library', # <--- ligne à ajouter
]

Nous devons maintenant ajouter le code dans le projet, ainsi nous allons créer un modèle et une vue.

Dans le fichierlibrary/models.py, ajoutez le code suivant :

from django.db import models
from django.utils import timezone

class Book(models.Model):
    author = models.CharField(max_length=50)
    title = models.CharField(max_length=50)

    def __str__(self):
        return f'{self.author} | {self.title}'

Et dans le fichierlibrary/views.py, ajoutez le code qui permet d’afficher le contenu d’un livre à l’aide de la clé primaire du livre dans la base de données :

from django.shortcuts import get_object_or_404, render
from .models import Book

def book_infos(request, pk):
    book = get_object_or_404(Book, pk=pk)
    return render(request, "book_infos.html", {'book':book})

Vous pouvez maintenant mettre à jour le fichierurl.pydans le projet :

from django.contrib import admin
from django.urls import path
from library import views      # <--- ligne à ajouter

urlpatterns = [
    path('admin/', admin.site.urls),
    path('<int:pk>', views.book_infos, name="infos") # <--- ligne à ajouter
]

Enfin, pour terminer le code de l’application, vous devez créer un répertoiretemplates/dans le répertoire de l’application pour ajouter votre fichier HTML. Vous allez donc pouvoir créer le fichierbook_infos.htmldans ce répertoire qui permettra d’afficher les informations d’un livre.

Créez le fichierlibrary/templates/book_infos.html:

<p>{{ book.author }} | {{ book.title }}</p>

Une fois l’implémentation de l’application terminée, vous pouvez migrer la base de données et lancer l’application.

La commande suivante permet de créer la migration ou mettre à jour les nouvelles modifications en fonction des modèles :

python manage.py makemigrations 

Vous pouvez ensuite exécuter la migration à l’aide de la commande ci-dessous :

python manage.py migrate

Si vous le souhaitez, vous pouvez lancer l’application pour tester que tout fonctionne correctement :

python manage.py runserver

Vous avez enfin terminé le code !

Configurez pytest-django

Avant de démarrer l'implémentation de la suite de tests, créez un fichierpytest.inidans le répertoire racine qui contient le fichiermanage.py. Ce fichier contiendra l’emplacement du fichier de setting (setting.py) du projet. Dans notre cas, le fichiersetting.pyse trouve dans le répertoiredjango_pytest.

Ajoutez dans le fichierpytest.ini:

[pytest]
DJANGO_SETTINGS_MODULE = django_pytest.settings

Vous pouvez aussi ajouter le format de vos fichiers de test pour faciliter le lancement des tests avec la commande pytest. Ajoutez encore une ligne avec le format que vous utilisez dans votre projet (tests.py,test_*.pyou*_tests.py).

J’utilise personnellement le formattest_*, je vais donc ajouter :

python_files = test_*

Lancez une requête

Le framework Django fournit un client de test afin de pouvoir simuler les requêtes HTTP sur notre application. Vous pouvez avoir accès à ce client de test grâce à l’importation et l’instanciation suivantes :

from django.test import Client
client = Client()

Ce client vous permet de simuler vos requêtesGETouPOSTen fournissant le chemin et les données utiles. 

Faites une requête GET

Vous pouvez faire une requêteGETen effectuant une instruction de ce type :

client = Client()
client.get('/path/', {'key1': 'data1', 'key2': 'data2'})

C’est équivalent à une requêteGETsur l’URL :/path/?key1=data1&key2=data2.

Faites une requête POST

De la même manière, vous pouvez faire une requêtePOSTutilisant une instruction de ce type :

client = Client()
client.post('/path/', {'key1': 'data1', 'key2': 'data2'})

C’est équivalent à une requêtePOSTsur l’URL/path/et avec une en-tête HTTP contenantkey1=data1&key2=data2.

Ainsi, vous avez deux méthodesget()etpost(), qui permettent respectivement de faire une requêteGETetPOST. Vous devez placer en premier argument le chemin, et en deuxième argument un dictionnaire contenant les données si nécessaire.

Gérez la base de données

Avant même de commencer à mettre en place votre premier test unitaire sur votre projet, vous devez savoir comment gérer les éléments de la base de données. N’oubliez pas que vous êtes en train de faire des tests unitaires, il ne faut donc pas utiliser la base de données de production, et vous ne pouvez pas créer une base de données à chaque fois que vous souhaitez lancer vos tests.

Pytest fournit une fonctionnalité qui permet de mettre en place une base de données temporaire qui se construit lors de l’exécution du test, et qui se vide à la fin du test. Il vous suffit donc de créer les éléments nécessaires dans votre base de données dans le test, et ensuite de lancer votre scénario.

Pour cela, vous devez spécifier le décorateur@pytest.mark.django_dbavant la définition de votre test.

Concernant l’emplacement des tests sur un projet Django, ils peuvent être placés dans une arborescence de tests à la racine du projet ou dans le répertoire de chaque application.

Vous allez sans tarder voir comment l’utiliser dans les tests suivants.

Testez les URL

Vous allez tout d’abord créer un dossiertests/dans l’applicationlibraryqui contiendra l’ensemble des tests de l’application. Vous pouvez ensuite créer un fichiertest_urls.pyqui contiendra les tests concernant le fichierurls.py.

Le test vérifiera que le path de l’URL et le nom de la vue sont corrects.

import pytest

from django.urls import reverse, resolve
from library.models import Book

@pytest.mark.django_db    
def test_book_infos_url():
    Book.objects.create(author = "Jules Verne",
                        title = "20 milles lieues sous les mers")
    path = reverse('infos', kwargs={'pk':1})
    
    assert path == "/1"
    assert resolve(path).view_name == "infos"

Ne vous inquiétez pas, je vais vous expliquer chaque ligne du code :

  • Book.objects.create(author = "Jules Verne", title = "20 mille lieues sous les mers")  : Crée un livre avec le modèleBook.

  • path = reverse('infos', kwargs={'pk':1}): Génère l’URL à l’aide du nom de la vue passé en paramètre.

  • assert path == "/1": Vérifie si l’URL est correcte.

  • assert resolve(path).view_name == "infos": Vérifie si le nom de la vue est correct et que l’URL correspond bien au nom de la vue.

Testez les vues

Restez dans le même répertoire de tests et ajoutez maintenant un fichier de test pour vérifier les vues (library/tests/test_views.py).

Vous allez vérifier que le contenu renvoyé par la vue et le code d’état HTTP sont valides. Voici le code qui permet de vérifier ça :

import pytest

from django.urls import reverse
from django.test import Client
from library.models import Book
from pytest_django.asserts import assertTemplateUsed

@pytest.mark.django_db  
def test_book_infos_view():
    client = Client()
    Book.objects.create(author = "Jules Verne",
                        title = "20 milles lieues sous les mers")
    path = reverse('infos', kwargs={'pk':1})
    response = client.get(path)
    content = response.content.decode()
    expected_content = "<p>Jules Verne | 20 milles lieues sous les mers</p>"

    assert content == expected_content
    assert response.status_code == 200
    assertTemplateUsed(response, "book_infos.html")

C’est parti pour une petite explication des nouvelles lignes de code :

  • client = Client(): Crée un client de test. Ce client se comportera comme un navigateur simplifié. Notez que vous n’avez pas besoin de lancer le serveur de Django avec ce client de test.

  • response = client.get(path): Fait une requête sur l’URL récupérée grâce à la fonction  reverse().

  • content = response.content.decode(): Décode la réponse HTML renvoyée par la vue.

  • expected_content = …: Spécifie la réponse attendue. 

  • assert content == expected_content: Vérifie que la réponse de la vue et la réponse attendue sont égales.

  • assert response.status_code == 200: Vérifie que le code d’état HTTP est égal à 200. 

  • assertTemplateUsed(response, "book_infos.html"): Vérifie que le gabarit du nom indiqué a été utilisé pour produire la réponse.

Testez les modèles

Vous allez continuer à travailler dans le répertoire de test et ajouter un nouveau fichier pour tester les modèles de l’application. Dans votre cas, vous n'avez qu’un seul modèle à tester.

J’ai mis en place un test qui permet de vérifier le modèleBook. Nous pourrons vérifier cela grâce à la fonction spéciale__str__de la classeBook, qui dans notre cas renvoie les informations de l’instance. Nous allons nous aider de la fonctioncreate(), qui renvoie justement l’instance du modèle au moment de l’insertion de la donnée en base.

Copiez le code ci-dessous et testez-le :

import pytest

from django.test import Client
from library.models import Book

@pytest.mark.django_db  
def test_book_model():
    client = Client()
    book = Book.objects.create(
               author = "Jules Verne",
               title = "20 milles lieues sous les mers")
    expected_value = "Jules Verne | 20 milles lieues sous les mers"
    assert str(book) == expected_value

À vous de jouer !

Je crois qu’il est temps de passer aux choses sérieuses ! Pour cette mission, prenons notre projet Django. Je vous invite à regarder la vidéo ci-dessous pour en apprendre plus sur l’application : 

Et comme je suis sympa, je vous propose un premier exemple de test unitaire sur ce même projet :

Votre mission :

  • Rédigez un plan de test pour ce projet.

  • Créez la suite de tests sur l’ensemble du projet à l’aide de pytest-django. 

Retrouvez une proposition de correction sur GitHub !

En résumé

  • La configuration de pytest-django nécessite la création du fichierpytest.inicontenant le chemin vers le fichier de setting du projet. Il faut spécifier le chemin grâce à la variableDJANGO_SETTINGS_MODULE. Autrement, les tests ne se lanceront pas.

  • Les URL peuvent être testées à l’aide de la méthoderesolve()et l’attributview_name.

  • L’ensemble des views retourne des données et un code de retour HTTP qui peuvent être testés dans un test unitaire. La valeur de retour de la requête fournit les attributscontent etstatus_code, qui contiennent respectivement les données et le code de retour HTTP.

  • Les modèles peuvent être testés à l’aide de la méthode spéciale__str__qui peut être implémentée pour renvoyer l’ensemble des informations du modèle.

Nous avons terminé avec les spécificités liées aux applications web. Je vous propose donc de continuer à approfondir vos connaissances sur le framework Pytest. Je vous attends dans le prochain chapitre !

Example of certificate of achievement
Example of certificate of achievement