Un Generador de tests es un software específico de cada track que genera automáticamente los tests de un ejercicio de práctica. Lo hace convirtiendo los casos de test en JSON del ejercicio a tests en el lenguaje del track.
Algunos beneficios de tener un Generador de tests son:
En general, se ejecuta un Generador de tests para una de dos cosas:
Agregar un Generador de tests para un ejercicio nuevo permite generar su archivo o archivos de tests. Siempre que el Generador de tests ya esté implementado, generar los tests del ejercicio nuevo será (mucho) menos trabajo que escribirlos desde cero.
Una vez que un ejercicio tiene un Generador de tests, puedes volver a ejecutarlo para actualizar o sincronizar el ejercicio con sus datos canónicos más recientes. Recomendamos hacerlo de forma periódica, para revisar si hay casos de test problemáticos que deban actualizarse o tests nuevos que quieras incluir.
Hay dos posibles puntos de partida al implementar un Generador de tests para un ejercicio:
Si ya existen tests, implementa el Generador de tests de manera que los tests que genere no rompan las soluciones existentes.
En términos generales, los archivos de tests se generan de una de dos formas:
Hemos visto que el enfoque basado en código da como resultado 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 archivo tests.toml del ejercicioEl beneficio clave de esta configuración es que cada ejercicio tiene su propia plantilla, lo cual:
Al diseñar el Generador de tests, procura:
Por lo general, el Generador de tests está escrito (en su mayoría) en el lenguaje del track.
Si bien 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 recomendamos usar el lenguaje del track siempre que sea posible, porque hace que mantenerlo o contribuir sea más fácil.
Si tu track tiene herramientas para dar formato al código, considera ejecutarlas como un paso de posprocesamiento después de renderizar tu plantilla.
Los datos principales con los que trabaja el Generador de tests son el canonical-data.json file de un ejercicio.
Este archivo se define en el repo exercism/problem-specifications, que define metadatos compartidos para muchos de los ejercicios de Exercism.
¡No todos los ejercicios tienen un archivo canonical-data.json!
Si no lo tienen, deberás 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) corresponden uno a uno con los tests de tu track.
Cada caso de test tiene varias propiedades, y las más importantes son la descripción, la propiedad, el valor o los valores de entrada y el valor esperado. Aquí hay un ejemplo (parcial) del archivo 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 responsabilidad principal 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 tests en 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 archivo 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 dos cosas:
Puedes identificar el tipo de un elemento revisando si están presentes los campos que son exclusivos de un tipo de elemento.
Probablemente la mejor forma de hacerlo sea usar la clave "cases", que solo está presente en los grupos de casos de test.
Aquí hay 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 los tests, deberás:
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 son valores escalares (como números, Boolean o strings) u objetos simples.
Sin embargo, de vez en cuando también encontrarás valores más complejos que probablemente requieran algo de preprocesamiento, como lambdas en pseudocódigo, listas de operaciones que se deben realizar sobre el código del estudiante y más.
Los casos de test tienen un campo opcional scenarios.
El generador de tests puede usar este campo para tratar de forma especial ciertos casos de test.
El caso de uso más común 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.
La lista completa de escenarios está aquí.
Hay un par de opciones para leer los archivos 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 repo 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 obtener la ubicación de forma programática.Si tu track quiere agregar algunos casos de test adicionales y específicos del track (que no se encuentran en los datos canónicos), una opción es crear un archivo additional-test-cases.json, que el Generador de tests puede combinar con el archivo canonical-data.json antes de pasarlo a la plantilla para renderizarla.
El motor de plantillas que uses probablemente sea específico del track. Lo ideal es que tus plantillas sean lo más simples posible, así que no te preocupes por la duplicación de código y ese tipo de cosas.
Las plantillas reciben sus datos del Generador de tests, que itera sobre ellos para renderizarlas.
Para ayudar a que las plantillas se mantengan simples, puede ser útil hacer un poco de preprocesamiento del lado del Generador de tests o, si no, definir algunos «filtros» o el mecanismo de extensión que permitan tus plantillas.
configlet es la herramienta principal 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 usar junto con el Generador de tests y lograr flujos de trabajo muy potentes.
Querrás que usar el Generador de tests sea fácil y potente a la vez. Para eso, recomendamos crear uno o más archivos de script.
Eres libre de elegir el formato de archivo de script que mejor se adapte a tu track. Los scripts de shell y los scripts de PowerShell son opciones comunes que funcionan bien.
Aquí hay un ejemplo de un 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 construir un Generador de tests, te sugerimos echar 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 lugar para hacerla. Las discusiones del foro sobre el generador de tests de Rust y el de JavaScript también pueden ser útiles.
Recomendamos construir 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 simplemente pasaría esos datos a la plantilla.
Empieza enfocándote en un solo ejercicio, preferiblemente uno simple como leap.
Solo cuando eso funcione deberías agregar más ejercicios poco a poco.
Y trata de mantener el Generador de tests tan simple como sea posible.
Lo ideal es que quien contribuya pueda 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 o el CONTRIBUTING.md del track, o en el directorio del código del Generador de tests.