Pourquoi Tester Django avec Pytest ?
Dans le monde professionnel, tester son code est une pratique essentielle pour garantir la qualité et la stabilité de l'application. Lorsque vous travaillez sur un projet Django, il est crucial d'avoir des tests automatisés pour vous assurer que chaque partie du code fonctionne comme prévu. Pytest est un framework de test Python populaire qui offre une grande flexibilité et une simplicité d'utilisation, parfaitement adapté à tester Django.
Un cas concret où le testing est essentiel est lors de la mise en production d'une application web. Sans tests automatisés, il est difficile de s'assurer que toutes les fonctionnalités sont correctement développées et que l'application est stable avant son déploiement. Pytest simplifie ce processus en permettant de créer des tests rapides et fiables qui peuvent être exécutés facilement à chaque modification du code.
Prerequis
Avant de commencer à tester Django avec Pytest, vous devez avoir les éléments suivants :
- Python 3.6 ou plus récent
- Django 2.2 ou plus récent
- Pytest
- virtualenv (facultatif mais recommandé)
Outils à installer
Pour installer Pytest et Django, vous pouvez utiliser pip. Assurez-vous d'avoir une environnement virtuel activé :
pip install pytest django
Concepts fondamentaux
1. Structure de projet
Pytest utilise une structure simple et intuitive pour organiser les tests. Les fichiers de test doivent être nommés en commençant par test_ ou en se terminant par _test.py. Ils doivent être placés dans un répertoire appelé tests.
myproject/
├── manage.py
├── myapp/
│ ├── __init__.py
│ ├── models.py
│ └── tests.py
└── tests/
├── __init__.py
├── test_models.py
└── test_views.py
2. Fixture
Une fixture est une fonction qui fournit des données ou des objets à un test. Pytest permet de créer des fixtures pour faciliter le setup et le teardown des tests.
## myapp/tests.py
import pytest
from django.contrib.auth.models import User
@pytest.fixture
def user():
return User.objects.create_user(username='testuser', password='12345')
3. Assertions
Pytest utilise les assertions Python standard pour vérifier que les résultats des tests sont corrects.
## myapp/tests.py
def test_user_creation(user):
assert user.username == 'testuser'
assert user.password != '12345' # Vérifie que le mot de passe n'est pas stocké en clair
4. Paramétrisation des tests
Pytest permet d'exécuter les mêmes tests avec différents paramètres, ce qui est utile pour tester différentes conditions.
## myapp/tests.py
import pytest
@pytest.mark.parametrize("value, expected", [
(1, 2),
(-1, -2),
(0, 0)
])
def test_square(value, expected):
assert value * 2 == expected
Mise en pratique : projet fil rouge
Nous allons créer un petit gestionnaire de tâches avec Django et Pytest. Le projet comprendra les fonctionnalités suivantes :
- Créer une tâche
- Marquer une tâche comme terminée
- Supprimer une tâche
Étape 1 : Configuration du projet
Commencez par créer un nouveau projet Django et un application.
django-admin startproject mytaskmanager
cd mytaskmanager
python manage.py startapp tasks
Étape 2 : Création des modèles
Ajoutez les modèles nécessaires dans tasks/models.py.
## tasks/models.py
from django.db import models
class Task(models.Model):
title = models.CharField(max_length=255)
completed = models.BooleanField(default=False)
def __str__(self):
return self.title
Étape 3 : Migrations
Créez et appliquez les migrations pour le modèle.
python manage.py makemigrations tasks
python manage.py migrate
Étape 4 : Création des vues et des URLs
Ajoutez les vues et les URL dans tasks/views.py et tasks/urls.py.
## tasks/views.py
from django.shortcuts import render, redirect
from .models import Task
def task_list(request):
tasks = Task.objects.all()
return render(request, 'tasks/task_list.html', {'tasks': tasks})
def create_task(request):
if request.method == 'POST':
title = request.POST.get('title')
Task.objects.create(title=title)
return redirect('task_list')
return render(request, 'tasks/create_task.html')
def toggle_task(request, task_id):
task = Task.objects.get(id=task_id)
task.completed = not task.completed
task.save()
return redirect('task_list')
def delete_task(request, task_id):
task = Task.objects.get(id=task_id)
task.delete()
return redirect('task_list')
python
## tasks/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.task_list, name='task_list'),
path('create/', views.create_task, name='create_task'),
path('toggle/<int:task_id>/', views.toggle_task, name='toggle_task'),
path('delete/<int:task_id>/', views.delete_task, name='delete_task'),
]
Étape 5 : Templates
Créez les templates dans tasks/templates/tasks/.
<!-- tasks/templates/tasks/task_list.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Task List</title>
</head>
<body>
<h1>Task List</h1>
<ul>
{% for task in tasks %}
<li>
task.title
<a href="{% url 'toggle_task' task.id %}">{% if not task.completed %}Mark as Done{% else %}Unmark{% endif %}</a>
<a href="{% url 'delete_task' task.id %}">Delete</a>
</li>
{% endfor %}
</ul>
<a href="{% url 'create_task' %}">Create New Task</a>
</body>
</html>
<!-- tasks/templates/tasks/create_task.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Create Task</title>
</head>
<body>
<h1>Create Task</h1>
<form method="post">
{% csrf_token %}
<input type="text" name="title" placeholder="Task title" required>
<button type="submit">Create</button>
</form>
</body>
</html>
Étape 6 : Configuration des tests
Ajoutez les tests dans tasks/tests.py.
## tasks/tests.py
import pytest
from django.urls import reverse
from .models import Task
from .views import task_list, create_task, toggle_task, delete_task
@pytest.mark.django_db
def test_task_creation(client):
response = client.post(reverse('create_task'), {'title': 'Test Task'})
assert response.status_code == 302
assert Task.objects.count() == 1
@pytest.mark.django_db
def test_task_toggle(client, task_factory):
response = client.get(reverse('toggle_task', args=[task_factory().id]))
assert response.status_code == 302
assert Task.objects.get(id=task_factory().id).completed is True
@pytest.mark.django_db
def test_task_delete(client, task_factory):
response = client.get(reverse('delete_task', args=[task_factory().id]))
assert response.status_code == 302
assert Task.objects.count() == 0
Étape 7 : Exécution des tests
Exécutez les tests pour vous assurer qu'ils fonctionnent correctement.
pytest
Erreurs fréquentes et debugging
Voici quelques erreurs courantes lors de l'utilisation de Pytest avec Django :
Erreur 1 : ImportError: cannot import name 'settings' from 'django.conf'
Cela peut se produire si vous n'avez pas correctement configuré votre environnement de test.
## ❌ Mauvais
import settings
## ✅ Correct
from django.conf import settings
Erreur 2 : AttributeError: module 'myapp.tests' has no attribute 'test_task_creation'
Cela peut se produire si le nom du fichier de test ne suit pas les conventions Pytest.
## ❌ Mauvais
## myapp/tests.py
def test_task_creation():
pass
## ✅ Correct
## tasks/tests.py
import pytest
@pytest.mark.django_db
def test_task_creation(client):
pass
Erreur 3 : pytest: error: no tests found in path(s): .
Cela peut se produire si le répertoire tests n'est pas correctement configuré.
## ❌ Mauvais
myproject/
├── manage.py
└── myapp/
## ✅ Correct
myproject/
├── manage.py
└── myapp/
└── tests/
└── test_models.py
Pour aller plus loin
Voici quelques pistes pour approfondir votre compréhension et vos compétences en testant Django avec Pytest :
- Tests fonctionnels : Apprenez à créer des tests fonctionnels qui simulent les interactions utilisateur.
- Mocking et stubbing : Utilisez des mocks et stubs pour isoler le code sous test et faciliter la répétition des tests.
- Coverage de code : Utilisez des outils comme
pytest-covpour mesurer la couverture de code de vos tests.
Défi pratique
Créez un petit scraper en utilisant Django et Pytest pour extraire les données d'un site web et les stocker dans une base de données. Assurez-vous d'avoir des tests automatisés pour vérifier que le scraper fonctionne correctement.
Ce tutoriel vous a hopefully aidé à comprendre comment tester Django avec Pytest. N'hésitez pas à me poser des questions si vous avez besoin d'aide supplémentaire !