Estratégia de testes
Uma aplicação do Archestack é testada por camadas, desde o editor que assinala um erro enquanto escreve até ao pipeline que não faz deploy enquanto um teste falhar. Os próprios testes fazem parte da aplicação: vivem nos seus packages, viajam com ela para todos os espaços de trabalho e correm contra uma base de dados sandbox própria, por isso um teste nunca lê nem altera os dados reais de ninguém.
Cinco camadas
| Camada | O que apanha | Quando corre |
|---|---|---|
| O editor | Código C# e código de páginas que não compila, uma coluna ou Business Entity que não existe, um Script Module chamado com um argumento errado | Enquanto escreve |
| Simulações | Um script ou Business Event que faz a coisa errada, experimentado antes de ser guardado | Enquanto escreve um script |
| Casos de teste | Comportamento: a regra que um Business Event impõe, o que um Script Module devolve, o que uma Business Entity lê | A cada alteração, e antes de cada deployment |
| Verificações do schema | Uma alteração de tabela que perde dados, ou que faria um script tipado deixar de compilar | Antes de cada migração e de cada deployment |
| Suites agendadas | Tudo o que mudou à volta da aplicação: outro package, uma atualização, dados que uma regra não esperava | Todas as noites, ou em qualquer agendamento, em todos os ambientes |
As duas primeiras não exigem trabalho: o editor verifica com o mesmo compilador e o mesmo modelo
de dados com que a aplicação corre, e o botão Test de um Script Module e a
simulação de um Business Event correm em modo de simulação, em que as escritas através de
Db e Files são verificadas e registadas mas nunca feitas, e o email e as
notificações são registados mas não enviados. Tudo o mais que um script faz por si mesmo é real:
chamadas HTTP, System.IO e tudo o que um
pacote NuGet faz, como uma ligação a outra base de
dados. O resto desta página trata
da terceira camada, os testes que escreve, e de como as outras duas os correm.
Casos de teste: given, when, then
Um caso de teste é uma afirmação sobre a aplicação, em três partes: as linhas a pôr no lugar (given), a única coisa a correr (when) e o que tem de ser verdade depois (then). Os casos são agrupados em suites. Este, da demo Service Planner, prova que o quadro de planeamento marca um dia como sobrecarregado apenas quando as horas planeadas de um técnico ultrapassam a sua capacidade:
{
"name": "Week load flags a day over capacity",
"suite": "Service Planner",
"isEnabled": true,
"data": {
"description": "A day is overbooked only when its planned hours go over the technician's capacity.",
"given": [
{ "table": "service_technician", "rows": [
{ "name": "Ana Peeters", "daily_capacity_hours": 8 },
{ "name": "Bram Maes", "daily_capacity_hours": 8 }
] },
{ "table": "service_job", "rows": [
{ "title": "Heat pump install", "technician_id": 1, "planned_date": "2026-09-23", "estimated_hours": 5, "status": "Planned" },
{ "title": "Boiler service", "technician_id": 1, "planned_date": "2026-09-23", "estimated_hours": 4, "status": "Planned" },
{ "title": "Commissioning", "technician_id": 2, "planned_date": "2026-09-23", "estimated_hours": 8, "status": "Planned" }
] }
],
"when": { "kind": "ScriptModule", "name": "GetPlannerWeek", "parameters": { "week_start": "2026-09-21" } },
"then": [
{ "subject": "Result", "operator": "Succeeds" },
{ "subject": "Result", "operator": "Equals", "path": "technicians[0].days[2].overbooked", "expected": "true" },
{ "subject": "Result", "operator": "Equals", "path": "technicians[1].days[2].overbooked", "expected": "false" }
]
}
} Given
As linhas de que o caso precisa, tabela a tabela, escritas de cima para baixo numa sandbox vazia.
As linhas recebem os ids 1, 2, 3 pela ordem em que são escritas, e é para eles que aponta a chave
estrangeira de uma fixture posterior: acima, o technician_id 1 é a Ana. Por isso, uma
tabela para a qual outra fixture aponta vem primeiro, e uma chave gerada pela base de dados fica
de fora, tal como as colunas de auditoria e qualquer coluna cujo valor por omissão sirva. No
builder, escolher uma tabela preenche linhas de exemplo a partir dos nomes e dos tipos das suas
colunas, e uma chave estrangeira é um seletor sobre as linhas que as fixtures acima dela escrevem.
When
Exatamente um alvo, indicado pelo nome, nunca por um id:
- Script Module: corre um módulo com os parâmetros indicados.
- Business Entity: lê uma entidade, opcionalmente filtrada, para verificar o que uma página mostraria.
- Business Event: dispara um trigger numa linha, no momento indicado (BeforeCreate, AfterUpdate e assim por diante), tal como uma gravação faria.
- Script: C# livre com tudo o que um Script Module tem, para um caso que tem de fazer várias coisas antes de a afirmação poder ser verificada.
Then
Cada verificação é um sujeito, um operador e, normalmente, um valor esperado. Os sujeitos são
Result (o que o alvo devolveu), Rows (linhas lidas de uma tabela depois),
Output (o que o alvo registou), Error, Duration e Code. Os
operadores são Equals, NotEquals, Contains, NotContains, GreaterThan, LessThan, IsNull, IsNotNull,
Matches (uma expressão regular), RowCount, Succeeds e Fails. Um caminho como
technicians[0].days[2].overbooked entra num resultado, e um caminho que não leva a
lado nenhum faz falhar a verificação e diz onde parou.
Testar que algo é recusado
Muitas das regras de uma aplicação dizem que não: um Business Event recusa uma gravação que partiria alguma coisa. O caso para uma regra destas tenta a gravação e verifica que foi recusada, pela razão certa, e que nada foi escrito:
// A zero estimate is refused.csx: the target, a save the business event must stop
await Db.CreateAsync("service_job", new { title = "Check radiator valves", estimated_hours = 0 });
return "saved"; "then": [
{ "subject": "Result", "operator": "Fails" },
{ "subject": "Error", "operator": "Contains", "expected": "Estimated hours must be more than zero" },
{ "subject": "Rows", "operator": "RowCount", "table": "service_job", "expected": "0" }
] A segunda verificação é a que importa. Fails sozinho passa com qualquer erro, incluindo uma coluna mal escrita, e essa é a forma mais comum de um teste vermelho deixar, sem ninguém dar por isso, de testar o que quer que seja. Prenda a falha à sua mensagem.
Verificações de código
Uma verificação que um sujeito e um operador não conseguem exprimir é uma verificação de
código: C# que corre depois do alvo na mesma sandbox, com tudo o que um Script Module
tem, incluindo os conjuntos tipados em Db. Result, Output,
Error, DurationMs e Failed guardam o resultado do alvo, e
Assert diz o que esperava e o que encontrou quando uma verificação falha (Equal,
NotEqual, True, False, Null, NotNull, Contains, Count e Fail). A verificação falha quando lança
uma exceção, falha um Assert ou devolve false.
// Planning a job marks it Planned.then.1.csx: a check one subject and one operator cannot say
var job = await Db.ServiceJob.SingleAsync(j => j.Title == "Boiler service");
Assert.Equal("Planned", job.Status);
Assert.NotNull(job.PlannedDate, "a planned job has a date");
var sameDay = await Db.ServiceJob
.Where(j => j.TechnicianId == job.TechnicianId && j.PlannedDate == job.PlannedDate)
.SumAsync(j => j.EstimatedHours);
Assert.True(sameDay <= 8, $"the day holds {sameDay} hours");
Recorra a uma quando uma verificação precisa de várias linhas ao mesmo tempo, de contas sobre
elas ou de uma comparação entre duas tabelas. Num repositório, cada verificação de código é um
ficheiro próprio, <Case>.then.<n>.csx, editado com as mesmas
funcionalidades de C# que qualquer script.
A sandbox
Um teste nunca toca nos dados do espaço de trabalho. Uma execução redireciona a base de dados dos dados para uma sandbox, uma base de dados separada, criada quando é precisa com a forma da real, e todas as suas tabelas são esvaziadas antes de cada caso, por isso as únicas linhas que um caso vê são as suas fixtures e o que o seu alvo escreve. As definições continuam a vir do espaço de trabalho, por isso um teste exercita as Business Entities, os Business Events e os scripts que estão prestes a ser entregues. Cada espaço de trabalho tem quatro sandboxes, por isso quatro execuções podem correr ao mesmo tempo; uma quinta espera que uma fique livre.
- O email e as notificações são registados, não enviados. O resultado de cada caso lista o que os seus scripts teriam enviado por email e quem teriam notificado, por isso um caso também pode verificar isso.
- Os ficheiros ficam como estão. As escritas, movimentos e eliminações através de
Filesnunca chegam à pasta do workspace: a execução guarda-os para si, por isso ler um ficheiro acabado de escrever funciona. - As chamadas para fora são reais. Tudo o resto que um script faz por si mesmo acontece em cada execução: um pedido HTTP, uma ligação SMTP que abra,
System.IOe tudo o que um pacote NuGet faz, como uma ligação a outra base de dados, incluindo escritas. Mantenha as chamadas a outros sistemas fora do que os testes correm, ou aponte-as para um endpoint de teste ou para uma base de dados de teste. - O registo das execuções é real. Uma execução guardada regista o seu resultado no caso e na suite e escreve uma entrada no Event Log. Uma execução de um rascunho por guardar, a partir do botão Run do editor de casos ou do VS Code, não regista nada.
Onde vivem os testes
Um teste pertence ao package que testa. Viaja com esse package numa exportação, numa importação
ou pela Module Store, por isso um módulo chega a um sítio novo com a prova de que ainda funciona,
e tudo o que refere (o Script Module, a Business Entity, o Business Event) refere pelo nome, por
isso aponta para a coisa certa em qualquer espaço de trabalho. Num repositório, os testes de um
package são ficheiros: tests/<suite>/suite.json, um ficheiro JSON por caso em
cases/, e um .csx ao lado de um caso para um alvo Script ou uma
verificação de código. São revistos e integrados com merge como o código que testam.
Correr testes
- No builder, a página Test Suite lista as suites e os seus casos, e corre um caso, uma suite ou tudo, com o esperado face ao obtido de cada verificação que falhou. O Run do editor de casos experimenta o caso antes de ser guardado.
- No VS Code, Run Tests corre um caso, uma suite, um package ou todos os packages contra o projeto, quatro casos de cada vez, e mostra cada resultado na árvore e na vista Testing. Veja Testes no guia do VS Code.
- Num agendamento, uma suite com um cron corre sozinha, em qualquer ambiente em
que esteja instalada:
0 0 2 * * ?é todas as noites às duas. Cada execução escreve uma entrada no Event Log com o detalhe de cada verificação, e o seu último resultado fica na suite. - No pipeline, os testes de todos os packages correm contra um Archestack descartável construído a partir do repositório antes de se fazer deploy de qualquer coisa, e basta uma falha para parar o deployment. Veja Estratégia de deployment.
- A partir de um assistente de IA, o MCP Server dá a um modelo ligado o mesmo guia de testes e as ferramentas para escrever um caso, corrê-lo e corrigi-lo até passar.
Ler um resultado
| Resultado | Significa |
|---|---|
| Passed | Todas as verificações se confirmaram. |
| Failed | O alvo correu e uma verificação não se confirmou. Cada verificação que falhou diz o que esperava e o que encontrou. |
| Error | O caso nunca chegou às suas verificações: uma fixture não foi inserida, um alvo tem o nome errado, ou o alvo lançou uma exceção quando o caso não o esperava. Leia primeiro o erro. |
| Skipped | O caso está desativado, ou a sua suite está desativada. |
O que testar primeiro
- Os Business Events que protegem os dados. Cada regra que recusa uma gravação ou uma eliminação recebe um caso que é recusado e um caso que é permitido, para que a regra não falte nem seja demasiado rígida.
- Script Modules que calculam. Dadas algumas linhas, verifique os números que o módulo devolve, e os limites: um dia vazio, um dia exatamente cheio, um dia acima do limite.
- As leituras de que a aplicação depende. Um alvo Business Entity com um filtro, e um RowCount, provam que uma página mostra as linhas que deve e nenhuma que não deva.
- Fluxos com vários passos. Um alvo Script que faz o que um utilizador faria, e verificações de código sobre tudo o que devia ter acontecido a seguir.
Testes que continuam úteis
- Um caso, uma afirmação. Um caso que verifica dez coisas não diz qual das dez se partiu.
- Dê ao caso o nome da regra. "A zero estimate is refused" lê-se como uma especificação na lista de resultados.
- Verifique o comportamento, não a fixture. Contar as linhas que acabou de inserir testa o carregador de fixtures.
- Prenda cada falha à sua mensagem, como acima.
- Mantenha as fixtures pequenas. Só as linhas de que a afirmação precisa, sem chaves geradas.
- Mantenha os testes junto do código. Uma alteração e o teste que a prova pertencem ao mesmo package, e ao mesmo pull request.
Para onde seguir a partir daqui
Archestack no VS Code explica como correr testes a partir do editor, Estratégia de deployment o pipeline que os corre antes de cada deployment, e a referência os Business Events e os Script Modules de que trata a maioria dos testes.