Un générateur de tests est un logiciel propre à un parcours, qui génère automatiquement les tests d'un exercice d'entraînement. Pour ce faire, il convertit les cas de test JSON de l'exercice en tests dans le langage du parcours.
Voici quelques avantages d'un générateur de tests :
En général, on lance un générateur de tests pour :
Ajouter un générateur de tests pour un nouvel exercice permet de générer son ou ses fichiers de tests. À condition que le générateur de tests lui-même soit déjà implémenté, générer les tests du nouvel exercice demandera (bien) moins de travail que de les écrire à partir de zéro.
Une fois qu'un exercice dispose d'un générateur de tests, tu peux le relancer pour mettre à jour ou synchroniser l'exercice avec ses dernières données canoniques. Nous te recommandons de le faire régulièrement, pour vérifier s'il y a des cas de test problématiques à mettre à jour ou de nouveaux tests que tu pourrais vouloir inclure.
Il y a deux points de départ possibles lorsqu'on implémente un générateur de tests pour un exercice :
S'il existe déjà des tests, implémente le générateur de tests de sorte que les tests qu'il génère ne cassent pas les solutions existantes.
D'une manière générale, les fichiers de tests sont générés de deux façons :
Nous avons constaté que l'approche fondée sur le code produit un code de générateur de tests assez complexe, tandis que l'approche fondée sur les gabarits est plus simple.
Voici la marche à suivre recommandée :
include = false dans le fichier tests.toml de l'exerciceLe principal avantage de cette organisation est que chaque exercice dispose de son propre gabarit, ce qui :
Lors de la conception du générateur de tests, essaie de :
Le générateur de tests est généralement (en grande partie) écrit dans le langage du parcours.
Tu es libre d'utiliser d'autres langages, mais chaque langage supplémentaire rendra la maintenance du parcours ou les contributions plus difficiles. Par conséquent, nous recommandons d'utiliser le langage du parcours lorsque c'est possible, car cela facilite la maintenance et les contributions.
Si ton parcours dispose d'outils pour mettre en forme le code, envisage de lancer cette mise en forme comme étape de post-traitement après le rendu de ton gabarit.
La donnée centrale avec laquelle travaille le générateur de tests est le fichier canonical-data.json d'un exercice.
Ce fichier est défini dans le dépôt exercism/problem-specifications, qui définit des métadonnées partagées pour de nombreux exercices d'Exercism.
Tous les exercices n'ont pas de fichier canonical-data.json !
S'ils n'en ont pas, tu devras créer les tests manuellement, car il n'y a aucune donnée avec laquelle le générateur de tests puisse travailler.
Les données canoniques sont définies dans un objet JSON.
Cet objet contient un champ "cases" qui contient les cas de test.
Ces cas de test correspondent (normalement) un pour un aux tests de ton parcours.
Chaque cas de test possède plusieurs propriétés, dont les plus importantes sont la description, la propriété, la ou les valeurs d'entrée et la valeur attendue.
Voici un exemple (partiel) du fichier canonical-data.json de l'exercice leap :
{
"exercise": "leap",
"cases": [
{
"uuid": "6466b30d-519c-438e-935d-388224ab5223",
"description": "year not divisible by 4 in common year",
"property": "leapYear",
"input": {
"year": 2015
},
"expected": false
},
{
"uuid": "4fe9b84c-8e65-489e-970b-856d60b8b78e",
"description": "year divisible by 4, not divisible by 100 in leap year",
"property": "leapYear",
"input": {
"year": 1996
},
"expected": true
}
]
}
La principale responsabilité du générateur de tests est de transformer ces données JSON en tests propres au parcours. Voici comment les données JSON ci-dessus peuvent se traduire en code de test Nim :
import unittest
import leap
suite "Leap":
test "year not divisible by 4 in common year":
check isLeapYear(2015) == false
test "year divisible by 4, not divisible by 100 in leap year":
check isLeapYear(1996) == true
La structure du fichier canonical-data.json est bien documentée et il dispose aussi d'une définition de schéma JSON.
Certains exercices utilisent l'imbrication dans leurs données canoniques.
Cela signifie que chaque élément d'un tableau cases peut être soit :
Tu peux identifier le type d'un élément en vérifiant la présence de champs exclusifs à un type d'élément.
La meilleure façon de procéder est probablement d'utiliser la clé "cases", qui n'est présente que dans les groupes de cas de test.
Voici un exemple de cas de test imbriqués :
{
"cases": [
{
"uuid": "e9c93a78-c536-4750-a336-94583d23fafa",
"description": "data is retained",
"property": "data",
"input": {
"treeData": ["4"]
},
"expected": {
"data": "4",
"left": null,
"right": null
}
},
{
"description": "insert data at proper node",
"cases": [
{
"uuid": "7a95c9e8-69f6-476a-b0c4-4170cb3f7c91",
"description": "smaller number at left node",
"property": "data",
"input": {
"treeData": ["4", "2"]
},
"expected": {
"data": "4",
"left": {
"data": "2",
"left": null,
"right": null
},
"right": null
}
}
]
}
]
}
Si ton parcours ne prend pas en charge le regroupement des tests, tu devras :
cases pour ne conserver que les cas de test les plus profonds (les feuilles)Le contenu des clés input et expected d'un cas de test varie énormément.
Dans la plupart des cas, il s'agit de valeurs scalaires (comme des nombres, des booléens ou des strings) ou d'objets simples.
Il t'arrivera cependant de rencontrer des valeurs plus complexes qui demanderont sans doute un peu de prétraitement, comme des lambdas en pseudo-code, des tableaux d'opérations à effectuer sur le code des apprenants, entre autres.
Les cas de test ont un champ facultatif scenarios.
Ce champ permet au générateur de tests de traiter certains cas de test de manière particulière.
Le cas d'usage le plus courant consiste à ignorer certains types de tests, par exemple les tests avec le scénario "unicode", car le langage de ton parcours peut ne pas prendre en charge Unicode.
La liste complète des scénarios se trouve ici.
Il existe plusieurs options pour lire les fichiers canonical-data.json :
problem-specifications (par exemple https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/leap/canonical-data.json).problem-specifications comme sous-module Git au dépôt du parcours.configlet.
L'emplacement dépend du système de l'utilisateur, mais tu peux utiliser configlet info -o -v d | head -1 | cut -d " " -f 5 pour obtenir cet emplacement par programmation.Si ton parcours souhaite ajouter des cas de test supplémentaires propres au parcours (absents des données canoniques), une option consiste à créer un fichier additional-test-cases.json, que le générateur de tests pourra ensuite fusionner avec le fichier canonical-data.json avant de le passer au gabarit pour le rendu.
Le moteur de gabarits à utiliser sera probablement propre au parcours. Idéalement, tu voudras que tes gabarits soient aussi simples que possible, alors ne te soucie pas de la duplication de code et de ce genre de choses.
Les gabarits eux-mêmes reçoivent leurs données du générateur de tests, qu'ils parcourent pour effectuer le rendu.
Pour aider à garder les gabarits simples, il peut être utile d'effectuer un peu de prétraitement du côté du générateur de tests, ou bien de définir quelques « filtres » ou tout autre mécanisme d'extension que tes gabarits autorisent.
configlet est le principal outil de maintenance d'un parcours et peut servir à :
bin/configlet create --practice-exercise <slug>
tests.toml d'un exercice existant : exécute bin/configlet sync --tests --update --exercise <slug>
Cela fait de configlet un excellent outil à combiner avec le générateur de tests pour obtenir des workflows vraiment puissants.
Tu voudras rendre l'utilisation du générateur de tests à la fois simple et puissante. Pour cela, nous recommandons de créer un ou plusieurs fichiers de script.
Tu es libre de choisir le format de fichier de script qui convient le mieux à ton parcours. Les scripts shell et les scripts PowerShell sont des options courantes qui fonctionnent bien toutes les deux.
Voici un exemple de script shell qui combine configlet et un générateur de tests pour créer rapidement la structure d'un nouvel exercice :
bin/fetch-configlet
bin/configlet create --practice-exercise <slug>
path/to/test-generator <slug>
Avant de commencer à construire un générateur de tests, nous te suggérons de regarder quelques générateurs de tests existants pour te faire une idée de la façon dont d'autres parcours les ont implémentés :
Si tu as des questions, le forum est le meilleur endroit pour les poser. Les discussions du forum sur les générateurs de tests Rust et JavaScript pourront aussi t'être utiles.
Nous recommandons de construire le générateur de tests de façon incrémentale, en commençant par un produit minimum viable.
Une version minimale se contenterait de lire le fichier canonical-data.json d'un exercice et de passer ces données au gabarit.
Commence par te concentrer sur un seul exercice, de préférence un exercice simple comme leap.
Ce n'est qu'une fois que cela fonctionne que tu devrais ajouter progressivement d'autres exercices.
Et essaie de garder le générateur de tests aussi simple que possible.
Idéalement, un contributeur pourrait simplement coller ou modifier un gabarit existant sans avoir à comprendre le fonctionnement interne du générateur de tests.
La façon d'utiliser un générateur de tests ou d'y contribuer est propre à chaque parcours.
Cherche les instructions dans le README.md du parcours, son CONTRIBUTING.md ou le dossier contenant le code du générateur de tests.