06 TDD
Ce TP a pour vocation à vous apprendre à écrire des cas de test simples et efficaces pour vos contrôleurs d'application Django. Nous écrirons pour cela des classes de test, fondées sur unittest, avec une approche TDD.
TDD : Test driven development, développement dirigé par les tests
Le TDD (test driven development ou développement dirigé par les tests en français) est à la mode… tout le monde en parle, mais peu de développeurs l'utilisent réellement et efficacement.
Et pourtant, agilité rime avec TDD : quand on veut livrer souvent, vite et bien, l'une des meilleures stratégies est de se laisser guider par le contrat de service de nos composants : autrement dit, on commence par écrire les tests, on les regarde échouer lamentablement, puis on écrit le code et… on espère qu'ils n'échouent plus.
Qu'à cela ne tienne : dans la suite de ce cours, nous tâcherons de faire les choses correctement, et d'adopter cette approche aussi souvent que possible !
On commence par écrire les tests !
TDD messieurs dames, on en a déjà parlé… Nous allons donc dès maintenant, avant d'écrire le moindre contrôleur (view), écrire quelques tests qui nous aideront à mettre à plat ce que nous souhaitons coder.
Mais… quels tests ?
Des tests, OK, mais lesquels ? Pour tester nos contrôleurs (views), nous devons avoir une bonne idée de ce que va devoir faire notre application, du moins dans sa toute première version.
Puisque nous sommes sportifs et agiles, nous n'allons pas essayer de spécifier l'ensemble du logiciel tout de suite, mais quelques écrans fondamentaux.
Games Review est une application de revue de jeux vidéo : elle doit a minima permettre :
- d'afficher un écran d'accueil avec un listing des derniers jeux publiés
- de consulter chaque fiche de jeux
C'est tout pour le moment !
Préparation du jeu de test
Pour tester efficacement, nous avons besoin de quelques données concrètes : on appelle cela un jeu de données ou encore jeu d'essai. C'est ce que nous allons commencer par définir !
Pour ce faire nous allons utiliser une fonctionnalités très utile du framework de test à savoir les fixtures. Pour commencer créer quelques jeux et types via l'interface d'adminsitration.
C'est fait ? Très bien. Maintenant nous allons créer un fichier JSON qui contient un dump de nos données (au format JSON). Pour ce faire nous allons utilisé la commande dumpdata
$ python manage.py dumpdata games_review > games_review/fixtures/initial.json
On demande ici au framework de faire un dump des données de nos modèles définis dans l'application games_review. Et de placer tout ceci dans un fichier initial.json.
🚨 Il est possible que le dossier fixtures n'existes pas. Pensez à le créer avant de lancer la commande.
Le fichier tests.py
Au sein de notre application games_review (donc dans le répertoire games_review), Django a créé pour nous un fichier tests.py (comme si ce vilain bougre voulait nous inciter à tester… 👻).
Dans le fichier nous allons donc ajouter quelques tests
from django.test import TestCase
from django.urls import reverse
from django.db.models.query import QuerySet
from games_review.models import Game
class WebsiteTestCase(TestCase):
fixtures = ['initial.json', ]
def test_index_page(self):
response = self.client.get(reverse('games_review:index'))
self.assertContains(response, "Latests games")
self.assertEqual(type(response.context['latest_games']), QuerySet)
self.assertEqual(len(response.context['latest_games']), 3)
self.failUnlessEqual(response.status_code, 200)
self.assertTemplateUsed('games/list.html')
def test_post_page(self):
# Published news
game = Game.objects.first()
response = self.client.get(reverse('games_review:post', kwargs={'slug': game.slug}))
self.assertContains(response, game.name)
self.assertEqual(type(response.context['game']), Game)
self.failUnlessEqual(response.status_code, 200)
self.assertTemplateUsed('news/index.html')
Nous créeons une classe qui dérive de TestCase, cette classe prend en compte notre fichier de fixtures initial.
Les deux méthodes permettent de tester les deux pages à minima de notre application. L'objet client nous permet de simuler l'interaction entre notre application et notre serveur. La fonction reverse permet d'obtenir l'url à partir d'un routing. A partir de la réponse nous testons plusieurs éléments:
- assertContains: la réponse contient le contenu de la variable
- assertEqual: l'élément 1 est égale à l'élément 2
- failUnlessEqual: échoue si l'élément 1 n'est pas égale à l'élément 2
- assertTemplateUsed: échoue si le fichier de template utilisé n'est pas celui spécifié
Lancement des tests (on craint le pire…)
Il est temps à présent de lancer nos tests. Sans grand espoir de réussite : vu que nous n'avons pas écrit le code de l'application, il n'y a pas de risque qu'elle fonctionne (sinon, il faudrait revoir les tests, puisque ceci voudrait dire qu'ils sont totalement inutiles !).
Dans le répertoire de notre projet, nous allons utiliser le script manage.py pour demander le lancement des tests. Cette commande permet de lancer tous les tests, ou seulement les tests d'une application du projet, voire même d'une seule méthode de test au sein d'une application. Lançons donc tous les tests de notre projet:
$ python manage.py test
Nos deux tests échouent : pas d'inquiétude, c'est normal !
Dans le TP suivant nous mettrons en place le code des vues et corrigerons ces vilains tests qui échouent.