Un generador de tests es un programa específico de cada track que genera automáticamente los tests de un ejercicio de práctica. Lo hace convirtiendo los casos de test JSON del ejercicio en tests en el lenguaje del track.
Tener un generador de tests tiene varias ventajas:
Por lo general, un generador de tests se utiliza para una de estas dos cosas:
Añadir un generador de tests para un ejercicio nuevo permite generar su fichero o ficheros de tests. Siempre que el generador de tests ya esté implementado, generar los tests del nuevo ejercicio será (mucho) menos trabajo que escribirlos desde cero.
Cuando un ejercicio ya tiene un generador de tests, puedes volver a ejecutarlo para actualizar o sincronizar el ejercicio con sus datos canónicos más recientes. Te recomendamos hacerlo periódicamente, para comprobar si hay casos de test problemáticos que haya que actualizar o nuevos tests que quieras incluir.
Hay dos puntos de partida posibles a la hora de implementar un generador de tests para un ejercicio:
Si ya hay tests, implementa el generador de tests de forma que los tests que genere no rompan las soluciones existentes.
A grandes rasgos, los ficheros de tests se generan de una de estas dos formas:
Hemos comprobado que el enfoque basado en código da lugar a un código de generador de tests bastante complejo, mientras que el enfoque basado en plantillas es más sencillo.
Lo que recomendamos es el siguiente flujo:
include = false en el fichero tests.toml del ejercicioLa principal ventaja de esta configuración es que cada ejercicio tiene su propia plantilla, lo que:
A la hora de diseñar el generador de tests, intenta:
Por lo general, el generador de tests está (en su mayor parte) escrito en el lenguaje del track.
Aunque eres libre de usar otros lenguajes, cada lenguaje adicional hará que sea más difícil mantener el track o contribuir a él. Por eso te recomendamos usar el lenguaje del track siempre que sea posible, porque hace que mantenerlo o contribuir a él sea más fácil.
Si tu track tiene herramientas para formatear código, plantéate ejecutarlas como paso de posprocesado después de renderizar tu plantilla.
El dato principal con el que trabaja el generador de tests es el fichero canonical-data.json de un ejercicio.
Este fichero se define en el repositorio exercism/problem-specifications, que define metadatos compartidos para muchos de los ejercicios de Exercism.
¡No todos los ejercicios tienen un fichero canonical-data.json!
Si no lo tienen, tendrás que crear los tests manualmente, ya que no hay datos con los que pueda trabajar el generador de tests.
Los datos canónicos se definen en un objeto JSON.
Este objeto contiene un campo "cases" que contiene los casos de test.
Estos casos de test (normalmente) se corresponden uno a uno con los tests de tu track.
Cada caso de test tiene varias propiedades, de las cuales las más importantes son la descripción, la propiedad, el valor o valores de entrada y el valor esperado. Aquí tienes un ejemplo (parcial) del fichero canonical-data.json del ejercicio 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 principal responsabilidad del generador de tests es transformar estos datos JSON en tests específicos del track. Así podría traducirse el JSON anterior a código de test de 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 estructura del fichero canonical-data.json está bien documentada y también tiene una definición de esquema JSON.
Algunos ejercicios usan anidamiento en sus datos canónicos.
Esto significa que cada elemento de un array cases puede ser una de estas dos cosas:
Puedes identificar el tipo de un elemento comprobando si están presentes los campos exclusivos de uno de los tipos.
Probablemente la mejor forma de hacerlo sea usar la clave "cases", que solo está presente en las agrupaciones de casos de test.
Aquí tienes un ejemplo de casos de test anidados:
{
"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 tu track no admite agrupar tests, tendrás que:
cases para quedarte solo con los casos de test más internos (las hojas)El contenido de las claves input y expected de un caso de test varía mucho.
En la mayoría de los casos serán valores escalares (como números, booleanos o strings) u objetos simples.
Sin embargo, de vez en cuando también te encontrarás con valores más complejos que probablemente requieran algo de preprocesado, como lambdas en pseudocódigo, listas de operaciones que hay que aplicar al código del estudiante y más.
Los casos de test tienen un campo scenarios opcional.
El generador de tests puede usar este campo para tratar de forma especial ciertos casos de test.
El uso más habitual es ignorar ciertos tipos de tests, por ejemplo los tests con el escenario "unicode", ya que puede que el lenguaje de tu track no admita Unicode.
Puedes encontrar la lista completa de escenarios aquí.
Hay varias opciones para leer los ficheros canonical-data.json:
problem-specifications (por ejemplo, https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/leap/canonical-data.json).problem-specifications como submódulo de Git al repositorio del track.configlet.
La ubicación depende del sistema del usuario, pero puedes usar configlet info -o -v d | head -1 | cut -d " " -f 5 para obtenerla mediante programación.Si tu track quiere añadir algunos casos de test adicionales y específicos del track (que no están en los datos canónicos), una opción es crear un fichero additional-test-cases.json, que el generador de tests puede combinar con el fichero canonical-data.json antes de pasarlo a la plantilla para renderizarlo.
El motor de plantillas que se use probablemente sea específico del track. Lo ideal es que tus plantillas sean lo más sencillas posible, así que no te preocupes por la duplicación de código y ese tipo de cosas.
Las propias plantillas obtienen sus datos del generador de tests, sobre los que itera para renderizarlas.
Para ayudar a que las plantillas sigan siendo sencillas, puede ser útil hacer algo de preprocesado en el lado del generador de tests o definir algunos «filtros» o el mecanismo de extensión que permitan tus plantillas.
configlet es la principal herramienta de mantenimiento de tracks y se puede usar para:
bin/configlet create --practice-exercise <slug>
tests.toml de un ejercicio existente: ejecuta bin/configlet sync --tests --update --exercise <slug>
Esto hace de configlet una gran herramienta para combinar con el generador de tests y lograr flujos de trabajo realmente potentes.
Querrás que usar el generador de tests sea fácil y potente a la vez. Para ello, te recomendamos crear uno o varios ficheros de script.
Eres libre de elegir el formato de fichero de script que mejor le venga a tu track. Los scripts de shell y los scripts de PowerShell son opciones habituales que funcionan bien.
Aquí tienes un ejemplo de script de shell que combina configlet y un generador de tests para crear rápidamente la estructura de un ejercicio nuevo:
bin/fetch-configlet
bin/configlet create --practice-exercise <slug>
path/to/test-generator <slug>
Antes de empezar a crear un generador de tests, te sugerimos que eches un vistazo a un par de generadores de tests existentes para hacerte una idea de cómo los han implementado otros tracks:
Si tienes alguna pregunta, el foro es el mejor sitio para plantearla. También pueden resultarte útiles los debates del foro sobre el generador de tests de Rust y sobre los generadores de tests de JavaScript.
Te recomendamos crear el generador de tests de forma incremental, empezando por un producto mínimo viable.
Una versión mínima básica leería el canonical-data.json de un ejercicio y se limitaría a pasar esos datos a la plantilla.
Empieza centrándote en un solo ejercicio, preferiblemente uno sencillo como leap.
Solo cuando eso funcione deberías ir añadiendo más ejercicios poco a poco.
Y procura que el generador de tests sea lo más simple posible.
Lo ideal sería que un contribuidor pudiera simplemente pegar o modificar una plantilla existente sin tener que entender cómo funciona el generador de tests por dentro.
Cómo usar un generador de tests o contribuir a él es algo específico de cada track.
Busca las instrucciones en el README.md del track, en su CONTRIBUTING.md o en el directorio del código del generador de tests.