Um Gerador de Testes é um software específico de cada track que gera automaticamente os testes de um exercício de prática. Fá-lo convertendo os casos de teste JSON do exercício em testes na linguagem do track.
Algumas vantagens de ter um Gerador de Testes são:
Em geral, executa-se um Gerador de Testes para uma de duas coisas:
Adicionar um Gerador de Testes para um novo exercício permite gerar o(s) ficheiro(s) de teste. Partindo do princípio de que o próprio Gerador de Testes já está implementado, gerar os testes para o novo exercício dará (muito) menos trabalho do que escrevê-los de raiz.
Assim que um exercício tem um Gerador de Testes, podes voltar a executá-lo para atualizar/sincronizar o exercício com os dados canónicos mais recentes. Recomendamos que o faças periodicamente, para verificar se há casos de teste problemáticos que precisam de ser atualizados ou novos testes que queiras incluir.
Há dois pontos de partida possíveis quando implementas um Gerador de Testes para um exercício:
Se já existirem testes, implementa o Gerador de Testes de forma a que os testes que gera não quebrem as soluções existentes.
De um modo geral, os ficheiros de teste são gerados de duas formas:
Verificámos que a abordagem baseada em código torna o código do Gerador de Testes bastante complexo, ao passo que a abordagem baseada em templates é mais simples.
O que recomendamos é o seguinte fluxo:
include = false no ficheiro tests.toml do exercício
A principal vantagem desta configuração é que cada exercício tem o seu próprio template, o que:
Ao conceber o gerador de testes, tenta:
O Gerador de Testes é normalmente escrito (na sua maioria) na linguagem do track.
Embora sejas livre de usar outras linguagens, cada linguagem adicional torna mais difícil dar manutenção ao track ou contribuir para ele. Por isso, recomendamos que uses a linguagem do track sempre que possível, porque assim é mais fácil dar manutenção ou contribuir.
Se o teu track tiver ferramentas para formatar código, considera executá-las como um passo de pós-processamento depois de renderizares o template.
Os dados centrais com que o Gerador de Testes trabalha são o ficheiro canonical-data.json de um exercício.
Este ficheiro é definido no repositório exercism/problem-specifications, que define metadados partilhados para muitos dos exercícios do Exercism.
Nem todos os exercícios têm um ficheiro canonical-data.json!
Se não tiverem, tens de criar os testes manualmente, pois não há dados com que o Gerador de Testes possa trabalhar.
Os dados canónicos são definidos num objeto JSON.
Este objeto contém um campo "cases" que contém os casos de teste.
Estes casos de teste correspondem (normalmente) um a um aos testes do teu track.
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. Eis um exemplo (parcial) do ficheiro 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 estes dados JSON em testes específicos do track. Eis como o JSON acima poderia ser traduzido para 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 ficheiro canonical-data.json está bem documentada e tem também uma definição de esquema JSON.
Alguns exercícios usam aninhamento nos seus dados canónicos.
Isto significa que cada elemento de um array cases pode ser:
Podes identificar o tipo de um elemento verificando a presença de campos exclusivos de um dos tipos.
Provavelmente a melhor forma de o fazer é usar a chave "cases", que só existe nos grupos de casos de teste.
Eis 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 o teu track não suportar o agrupamento de testes, tens de:
cases para ficar apenas com os casos de teste mais internos (as folhas)O conteúdo das chaves input e expected dos casos de teste varia muito.
Na maioria dos casos, são valores escalares (como números, Boolean ou strings) ou objetos simples.
No entanto, ocasionalmente também encontras valores mais complexos que provavelmente exigem algum pré-processamento, como lambdas em pseudocódigo, listas de operações a realizar sobre o código dos alunos e muito mais.
Os casos de teste têm um campo opcional scenarios.
Este campo pode ser usado pelo gerador de testes para tratar certos casos de teste de forma especial.
O caso de utilização mais comum é ignorar certos tipos de testes, por exemplo testes com o cenário "unicode", pois a linguagem do teu track pode não suportar Unicode.
A lista completa de cenários pode ser consultada aqui.
Há algumas opções para ler os ficheiros canonical-data.json:
problem-specifications (por exemplo, https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/leap/canonical-data.json).problem-specifications como submódulo Git ao repositório do track.configlet.
A localização depende do sistema do utilizador, mas podes usar configlet info -o -v d | head -1 | cut -d " " -f 5 para obter a localização de forma programática.Se o teu track quiser adicionar alguns casos de teste adicionais, específicos do track (que não se encontram nos dados canónicos), uma opção é criar um ficheiro additional-test-cases.json, que o Gerador de Testes pode depois combinar com o ficheiro canonical-data.json antes de o passar ao template para renderização.
O motor de templates a usar será provavelmente específico do track. Idealmente, queres que os teus templates sejam o mais simples possível, por isso não te preocupes com a duplicação de código e coisas do género.
Os próprios templates recebem os seus dados do Gerador de Testes, sobre os quais este itera para os renderizar.
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 mecanismo de extensão que os teus templates permitam.
O configlet é a principal ferramenta de manutenção de tracks e pode ser usado para:
bin/configlet create --practice-exercise <slug>
tests.toml de um exercício existente: executa bin/configlet sync --tests --update --exercise <slug>
Isto faz do configlet uma excelente ferramenta para usar em conjunto com o Gerador de Testes, permitindo fluxos de trabalho bastante poderosos.
Vais querer que a utilização do Gerador de Testes seja fácil e poderosa. Para isso, recomendamos que cries um ou mais ficheiros de script.
És livre de escolher o formato de ficheiro de script que melhor se adapta ao teu track. Scripts de shell e scripts PowerShell são opções comuns, ambas funcionais.
Eis um exemplo de um script de shell que combina o configlet e um Gerador de Testes para criar rapidamente a estrutura de um novo exercício:
bin/fetch-configlet
bin/configlet create --practice-exercise <slug>
path/to/test-generator <slug>
Antes de começares a construir um Gerador de Testes, sugerimos que vejas alguns Geradores de Testes já existentes para perceberes como outras tracks os implementaram:
Se tiveres alguma dúvida, o fórum é o melhor sítio para a colocares. As discussões no fórum sobre o gerador de testes de Rust e sobre o de JavaScript também podem ser úteis.
Recomendamos construir o Gerador de Testes de forma incremental, começando por um Produto Mínimo Viável.
Uma versão mínima consistiria apenas em ler o canonical-data.json de um exercício e passar esses dados ao template.
Começa por te focar num único exercício, de preferência num simples como o leap.
Só quando isso estiver a funcionar é que deves acrescentar gradualmente mais exercícios.
E tenta manter o Gerador de Testes o mais simples possível.
Idealmente, um contribuidor poderia limitar-se a colar/modificar um template existente sem ter de perceber como o Gerador de Testes funciona internamente.
A forma de usar ou contribuir para um Gerador de Testes é específica de cada track.
Procura instruções no README.md do track, no CONTRIBUTING.md ou no diretório do código do Gerador de Testes.