Nouvel article concernant la qualité logiciel, aujourd'hui, nous allons mettre l'accent sur les données de test.
Cet article fait partie d'une série d'articles visant à énumérer les différents éléments de qualité à mettre en place au démarrage d'un projet.
Cet article sera assez court, c'est surtout un complément à la partie 5. Comme d'habitude, on ne rentrera pas dans les détails, le message étant plutôt : Pense à mettre en place tes solutions avant de commencer à coder.
Lorsqu'on va tester, on va avoir besoin de se mettre dans un état de départ, avec un jeu de données en particulier par exemple.
Les fixtures sont des fonctions ou des méthodes qui permettent de préparer l'environnement de test avant l'exécution de chaque test. C'est ce qu'on va utiliser pour initialiser des objets, des bases de données, des fichiers temporaires, etc.
Dans Django, les fixtures sont des fichiers de données, en JSON ou en YAML, que l'on va pouvoir charger avant d'effectuer les tests. On pourra charger des jeux de données spécifiques pour certains tests.
Par exemple, voici un fichier de fixture :
[
{
"model": "myapp.musician",
"pk": 1,
"fields": {
"name": "Joe Strummer",
"band": "The Clash",
"instrument": "Vocals, Guitar",
"years_active": "1976-1986, 2001-2002"
}
},
{
"model": "myapp.musician",
"pk": 2,
"fields": {
"name": "Dee Dee Ramone",
"band": "The Ramones",
"instrument": "Bass",
"years_active": "1974-1989"
}
}
]
Ces données pourront être chargées dans la base de données grâce à la commande manage.py loaddata fixtures/musicians.json
. On pourra aussi et surtout charger ces fichiers lors des tests unitaires, automatiquement.
from django.test import TestCase
class TestMusicians(TestCase):
fixtures = ['musicians.json'] # Spécifie le fichier de fixture à charger
def test_playing_guitar(self):
# Fait quelque chose avec les données chargées depuis la fixture
pass
Lorsqu'on fait des tests unitaires, on doit parfois tester du code qui a une dépendance à quelque chose externe au système : une API, la connexion à une base de données, à un système d'execution de code asynchrone, etc.
Il est important de simuler ces appels extérieurs, pour assurer la fiabilité du test. C'est là qu'on utilise les mocks.
Par exemple, on peut mocker une API avec un logiciel du type Wiremock. Le principe est de proposer une API statique, de pré-enregistrer des réponses à des requêtes à l'API externe, et de les retourner lors de chaque appel. Un serveur tourne localement, il suffit alors de changer la configuration du domaine de l'API lors des tests, en lui indiquant l'adresse locale (par exemple http://127.0.0.1:8888
).
On peut aussi mocker des méthodes ou des classes directement dans le code, pour éviter d'exécuter du code non désiré pour le test. Par exemple, plutôt que de proposer une vraie fausse API avec Wiremock, on pourrait juste monkeypatcher la méthode get
de la librairie request
juste avant d'exécuter notre test. C'est-à-dire que l'on va remplacer l'appel de cette méthode par une méthode qui va juste se contenter de retourner des valeurs.
Voici un exemple :
import pytest
import requests
def get_punk_bands(): # La méthode que l'on veut tester
response = requests.get('https://punkapi.com/api/v1/bands')
if response.status_code == 200:
return response.json()
else:
return None
def test_get_punk_bands(monkeypatch):
# Simule la réponse de l'API avec un objet Mock
mock_response = [{'name': 'The Clash'}, {'name': 'The Ramones'}]
mock_get = pytest.Mock(return_value=mock_response)
monkeypatch.setattr(requests, 'get', mock_get) # Ici, on dit que la méthode get de l'objet request va être remplacée par notre mock
# Appelle la fonction et vérifie la sortie
bands = get_punk_bands()
assert bands == mock_response
mock_get.assert_called_once_with('https://punkapi.com/api/v1/bands') # On s'assure que le mock a bien été appelé avec le paramètre attendu
Ainsi, on teste bien notre code, mais on s'arrête à partir du moment où l'on fait appel à un service externe.
Voilà, c'est assez court. Mais si je peux vous donner un conseil : dès que vous avez mis en place votre framework de test (ou vos frameworks si vous voulez utiliser plusieurs niveaux de tests), mettez en place vos outils de fixtures et de mocks (ou assurez-vous que ceux qui viennent avec vos outils fonctionnent bien). Et assurez-vous que vous sachiez les utiliser.
D'expérience, il est assez couteux de mettre en place ces outils en plein milieu d'un projet, surtout dans un contexte avec plusieurs collaborateurs. En effet, il y aura un coût d'adoption qui sera non négligeable, il peut y avoir le besoin de reprendre le code des tests, et ça sera beaucoup de souffrances pour un tout petit sujet. Comme dit le proverbe : "Bloquer sur les mocks, c'est moche." (proverbe provençal)
C'est tout pour aujourd'hui. Le prochain article conclura la série sur l'outillage pour les tests unitaires en abordant les factories.
2022 - tominardi.fr