Um Gerador de Testes é um software específico de cada trilha que gera automaticamente os testes de um exercício de prática. Ele faz isso convertendo os casos de teste em JSON do exercício para testes na linguagem da trilha.
Algumas vantagens de ter um Gerador de Testes:
Em geral, roda-se um Gerador de Testes para:
Adicionar um Gerador de Testes para um exercício novo permite gerar o(s) arquivo(s) de teste dele. Desde que o próprio Gerador de Testes já esteja implementado, gerar os testes do exercício novo dá (muito) menos trabalho do que escrevê-los do zero.
Quando um exercício passa a ter um Gerador de Testes, você pode rodá-lo de novo para atualizar/sincronizar o exercício com os dados canônicos mais recentes. Recomendamos fazer isso periodicamente, para verificar se há casos de teste problemáticos que precisam ser atualizados ou testes novos que você queira incluir.
Há dois pontos de partida possíveis ao implementar um Gerador de Testes para um exercício:
Se já existem testes, implemente o Gerador de Testes de forma que os testes que ele gera não quebrem as soluções existentes.
De modo geral, os arquivos de teste são gerados de duas maneiras:
Descobrimos que a abordagem baseada em código leva a um código de Gerador de Testes bem complexo, enquanto a abordagem baseada em templates é mais simples.
O que recomendamos é o seguinte fluxo:
include = false no arquivo tests.toml do exercícioO principal benefício dessa configuração é que cada exercício tem o seu próprio template, o que:
Ao projetar o gerador de testes, tente:
O Gerador de Testes normalmente é (em sua maior parte) escrito na linguagem da trilha.
Embora você seja livre para usar outras linguagens, cada linguagem a mais torna a trilha mais difícil de manter ou de contribuir. Por isso, recomendamos usar a linguagem da trilha sempre que possível, porque isso facilita a manutenção e a contribuição.
Se a sua trilha tem ferramentas para formatar código, considere rodá-las como uma etapa de pós-processamento depois de renderizar o seu template.
O dado central com que o Gerador de Testes trabalha é o arquivo canonical-data.json de um exercício.
Esse arquivo é definido no repositório exercism/problem-specifications, que define metadados compartilhados por muitos dos exercícios do Exercism.
Nem todos os exercícios têm um arquivo canonical-data.json!
Se não tiverem, você vai precisar criar os testes manualmente, já que não há dados com que o Gerador de Testes possa trabalhar.
Os dados canônicos são definidos em um objeto JSON.
Esse objeto contém um campo "cases" que contém os casos de teste.
Esses casos de teste (normalmente) correspondem um a um aos testes da sua trilha.
Cada caso de teste tem algumas propriedades, sendo a descrição, a propriedade, o(s) valor(es) de entrada e o valor esperado as mais importantes. Veja um exemplo (parcial) do arquivo canonical-data.json do exercício 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
}
]
}
A principal responsabilidade do Gerador de Testes é transformar esses dados JSON em testes específicos da trilha. Veja como o JSON acima poderia se transformar em código de teste em 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
A estrutura do arquivo canonical-data.json está bem documentada e também tem uma definição de JSON schema.
Alguns exercícios usam aninhamento nos seus dados canônicos.
Isso significa que cada elemento de um array cases pode ser:
Você pode identificar o tipo de um elemento verificando a presença de campos exclusivos de um dos tipos.
Provavelmente a melhor forma de fazer isso é usar a chave "cases", que só existe em grupos de casos de teste.
Veja um exemplo de casos de teste aninhados:
{
"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
}
}
]
}
]
}
Se a sua trilha não oferece suporte a testes agrupados, você vai precisar:
cases para ficar apenas com os casos de teste mais internos (as folhas)O conteúdo das chaves input e expected de um caso de teste varia bastante.
Na maioria dos casos, são valores escalares (como números, Boolean ou strings) ou objetos simples.
No entanto, às vezes você também encontra valores mais complexos que provavelmente exigem um pouco de pré-processamento, como lambdas em pseudocódigo, listas de operações a executar no código dos estudantes e mais.
Os casos de teste têm um campo opcional scenarios.
Esse campo pode ser usado pelo gerador de testes para tratar certos casos de teste de forma especial.
O uso mais comum é ignorar certos tipos de teste, por exemplo testes com o cenário "unicode", já que a linguagem da sua trilha pode não oferecer suporte a Unicode.
A lista completa de cenários está aqui.
Há algumas opções para ler os arquivos canonical-data.json:
problem-specifications (por exemplo, https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/leap/canonical-data.json).problem-specifications como um submódulo Git no repositório da trilha.configlet.
O local depende do sistema do usuário, mas você pode usar configlet info -o -v d | head -1 | cut -d " " -f 5 para obter o local programaticamente.Se a sua trilha quiser adicionar alguns casos de teste extras, específicos da trilha (que não estão nos dados canônicos), uma opção é criar um arquivo additional-test-cases.json, que o Gerador de Testes pode mesclar com o arquivo canonical-data.json antes de passá-lo ao template para renderização.
O motor de templates a usar provavelmente será específico da trilha. O ideal é que os seus templates sejam o mais diretos possível, então não se preocupe com duplicação de código e coisas do tipo.
Os próprios templates recebem seus dados do Gerador de Testes, que itera sobre eles para renderizá-los.
Para ajudar a manter os templates simples, pode ser útil fazer um pouco de pré-processamento do lado do Gerador de Testes ou então definir alguns "filtros" ou qualquer outro mecanismo de extensão que os seus templates permitam.
O configlet é a principal ferramenta de manutenção de trilhas e pode ser usado para:
bin/configlet create --practice-exercise <slug>
tests.toml de um exercício existente: rode bin/configlet sync --tests --update --exercise <slug>
Isso faz do configlet uma ótima ferramenta para usar junto com o Gerador de Testes em fluxos de trabalho realmente poderosos.
Você vai querer que usar o Gerador de Testes seja fácil e poderoso ao mesmo tempo. Para isso, recomendamos criar um ou mais arquivos de script.
Você é livre para escolher o formato de arquivo de script que melhor se encaixa na sua trilha. Scripts shell e scripts PowerShell são opções comuns, e ambas funcionam bem.
Veja um exemplo de script shell que combina o configlet e um Gerador de Testes para criar rapidamente a estrutura de um exercício novo:
bin/fetch-configlet
bin/configlet create --practice-exercise <slug>
path/to/test-generator <slug>
Antes de começar a construir um Gerador de Testes, sugerimos que você olhe alguns Geradores de Testes que já existem para ter uma ideia de como outras trilhas os implementaram:
Se você tiver dúvidas, o fórum é o melhor lugar para perguntá-las. As discussões no fórum sobre o gerador de testes do Rust e o do JavaScript também podem ajudar.
Recomendamos construir o Gerador de Testes de forma incremental, começando com um Produto Mínimo Viável.
A versão mínima possível leria o canonical-data.json de um exercício e simplesmente passaria esses dados ao template.
Comece focando em um único exercício, de preferência um simples como o leap.
Só depois que esse estiver funcionando você deve adicionar mais exercícios aos poucos.
E tente manter o Gerador de Testes o mais simples possível.
O ideal é que quem contribui possa simplesmente colar/modificar um template existente sem precisar entender como o Gerador de Testes funciona por dentro.
Como usar ou contribuir com um Gerador de Testes é específico de cada trilha.
Procure instruções no README.md ou CONTRIBUTING.md da trilha, ou no diretório do código do Gerador de Testes.