Strategia di test
Un'app Archestack si testa a livelli, dall'editor che segnala un errore mentre scrivi alla pipeline che non fa il deploy finché un test fallisce. I test stessi fanno parte dell'app: vivono nei suoi package, viaggiano con essa in ogni workspace e girano su un database sandbox tutto loro, così un test non legge né modifica mai i dati reali di nessuno.
Cinque livelli
| Livello | Cosa individua | Quando gira |
|---|---|---|
| L'editor | Codice C# e codice delle pagine che non compila, una colonna o una business entity che non esiste, uno script module chiamato con un argomento sbagliato | Mentre scrivi |
| Simulazioni | Uno script o un business event che fa la cosa sbagliata, provato prima di essere salvato | Mentre scrivi uno script |
| Test case | Il comportamento: la regola che un business event impone, cosa restituisce uno script module, cosa legge una business entity | A ogni modifica, e prima di ogni deployment |
| Controlli dello schema | Una modifica a una tabella che perde dati, o che farebbe smettere di compilare uno script tipizzato | Prima di ogni migration e di ogni deployment |
| Suite pianificate | Qualsiasi cosa sia cambiata intorno all'app: un altro package, un aggiornamento, dati che una regola non si aspettava | Ogni notte, o con qualsiasi pianificazione, in ogni ambiente |
I primi due non richiedono lavoro: l'editor controlla con lo stesso compilatore e lo stesso
modello dati con cui gira l'app, e il pulsante Test di uno script module e la
simulazione di un business event girano in modalità simulazione, in cui le scritture tramite
Db e Files vengono controllate e registrate ma mai eseguite, e mail e
notifiche vengono registrate ma non inviate. Tutto il resto che uno script fa da sé, invece, è
reale: le chiamate HTTP, System.IO e tutto ciò che fa un
pacchetto NuGet, come una connessione a un altro
database. Il resto di questa pagina
parla del terzo livello, i test che scrivi, e di come li eseguono gli altri due.
Test case: given, when, then
Un test case è un'affermazione sull'app, in tre parti: le righe da preparare (given), l'unica cosa da eseguire (when) e ciò che deve essere vero dopo (then). I test case sono raggruppati in suite. Questo, dalla demo Service Planner, dimostra che la bacheca di pianificazione segna un giorno come sovraccarico solo quando le ore pianificate di un tecnico superano la sua capacità:
{
"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
Le righe di cui il test case ha bisogno, tabella per tabella, scritte dall'alto in basso in una
sandbox vuota. Le righe ricevono gli id 1, 2, 3 nell'ordine in cui vengono scritte, ed è a questi
che punta la foreign key di una fixture successiva: sopra, technician_id 1 è Ana.
Quindi una tabella a cui punta un'altra fixture va per prima, e una chiave generata dal database
viene omessa, così come le colonne di audit e qualsiasi colonna il cui default va bene. Nel
builder, scegliere una tabella compila righe di esempio a partire dai nomi e dai tipi delle sue
colonne, e una foreign key è un selettore tra le righe scritte dalle fixture sopra di essa.
When
Esattamente un target, indicato con il suo nome, mai con un id:
- Script module: esegue un modulo con i parametri indicati.
- Business entity: rilegge un'entity, eventualmente filtrata, per controllare cosa mostrerebbe una pagina.
- Business event: fa scattare un trigger su una riga, nel momento indicato (BeforeCreate, AfterUpdate e così via), come farebbe un salvataggio.
- Script: C# libero con tutto ciò che ha uno script module, per un test case che deve fare diverse cose prima di poter verificare l'affermazione.
Then
Ogni controllo è un soggetto, un operatore e di solito un valore atteso. I soggetti sono
Result (ciò che il target ha restituito), Rows (le righe rilette da una tabella
dopo), Output (ciò che il target ha scritto nel log), Error, Duration e
Code. Gli operatori sono Equals, NotEquals, Contains, NotContains, GreaterThan, LessThan,
IsNull, IsNotNull, Matches (un'espressione regolare), RowCount, Succeeds e Fails. Un percorso come
technicians[0].days[2].overbooked entra dentro un risultato, e un percorso che non
porta da nessuna parte fa fallire il controllo e dice dove si è fermato.
Testare che qualcosa venga rifiutato
Molte regole di un'app dicono di no: un business event rifiuta un salvataggio che romperebbe qualcosa. Il test case di una regola del genere prova il salvataggio e controlla che sia stato rifiutato, per il motivo giusto, e che non sia stato scritto niente:
// 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" }
] Il secondo controllo è quello che conta. Fails da solo passa con qualsiasi errore, compresa una colonna scritta male, ed è il modo più comune in cui un test rosso smette in silenzio di testare qualcosa. Lega il fallimento al suo messaggio.
Controlli in codice
Un controllo che un solo soggetto e un solo operatore non riescono a esprimere è un
controllo in codice: C# che gira dopo il target nella stessa sandbox, con tutto
ciò che ha uno script module, compresi i set tipizzati su Db. Result,
Output, Error, DurationMs e Failed contengono
l'esito del target, e Assert dice cosa si aspettava e cosa ha trovato quando un
controllo fallisce (Equal, NotEqual, True, False, Null, NotNull, Contains, Count e Fail). Il
controllo fallisce quando lancia un'eccezione, quando fallisce un Assert o quando restituisce
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");
Usane uno quando un controllo ha bisogno di più righe insieme, di calcoli su di esse o di un
confronto tra due tabelle. In un repository ogni controllo in codice è un file a sé,
<Case>.then.<n>.csx, che si modifica con le stesse funzionalità C# di
qualsiasi script.
La sandbox
Un test non tocca mai i dati del workspace. Un'esecuzione reindirizza il database dei dati verso una sandbox, un database separato creato al bisogno con la stessa forma di quello reale, e ogni sua tabella viene svuotata prima di ogni test case, così le uniche righe che un test case vede sono le sue fixture e ciò che scrive il suo target. Le definizioni vengono comunque dal workspace, quindi un test mette alla prova le business entity, i business event e gli script che stanno per essere rilasciati. Ogni workspace ha quattro sandbox, quindi quattro esecuzioni possono girare insieme; una quinta aspetta che se ne liberi una.
- Mail e notifiche vengono registrate, non inviate. Il risultato di ogni test case elenca cosa avrebbero spedito per mail i suoi script e chi avrebbero notificato, così un test case può controllare anche questo.
- I file restano come sono. Scritture, spostamenti ed eliminazioni tramite
Filesnon raggiungono mai la cartella del workspace: l'esecuzione li tiene per sé, quindi rileggere un file appena scritto funziona. - Le chiamate verso l'esterno sono reali. Tutto il resto che uno script fa da sé avviene a ogni esecuzione: una richiesta HTTP, una connessione SMTP che apre,
System.IOe tutto ciò che fa un pacchetto NuGet, come una connessione a un altro database, scritture comprese. Tieni le chiamate ad altri sistemi fuori da ciò che eseguono i test, oppure puntale a un endpoint di test o a un database di test. - Il registro delle esecuzioni è reale. Un'esecuzione salvata registra il suo risultato sul test case e sulla suite e scrive una voce nell'Event Log. L'esecuzione di una bozza non salvata, dal pulsante Run dell'editor del test case o da VS Code, non registra niente.
Dove vivono i test
Un test appartiene al package che testa. Viaggia con quel package attraverso un export, un import
o il Module Store, così un modulo arriva in un posto nuovo con la prova che funziona ancora, e
tutto ciò che nomina (lo script module, la business entity, il business event) lo nomina per
nome, quindi punta alla cosa giusta in qualsiasi workspace. In un repository, i test di un package
sono file: tests/<suite>/suite.json, un file JSON per ogni test case sotto
cases/, e un .csx accanto a un test case per un target Script o un
controllo in codice. Passano per revisione e merge come il codice che testano.
Eseguire i test
- Nel builder, la pagina Test Suite elenca le suite e i loro test case, ed esegue un test case, una suite o tutto, con l'atteso contro l'effettivo di ogni controllo fallito. Il pulsante Run dell'editor del test case prova il test case prima che venga salvato.
- In VS Code, Run Tests esegue un test case, una suite, un package o tutti i package sul progetto, quattro test case alla volta, e mostra ogni risultato nell'albero e nella vista Testing. Vedi Test nella guida a VS Code.
- Su pianificazione, una suite con un cron gira da sola, in qualsiasi ambiente
sia installata:
0 0 2 * * ?è ogni notte alle due. Ogni esecuzione scrive una voce nell'Event Log con il dettaglio di ogni controllo, e il suo ultimo risultato è sulla suite. - Nella pipeline, i test di ogni package girano su un Archestack usa e getta costruito dal repository prima che venga fatto il deploy di qualsiasi cosa, e un solo fallimento ferma il deployment. Vedi Strategia di deployment.
- Da un assistente AI, l'MCP Server dà a un modello connesso la stessa guida ai test e gli strumenti per scrivere un test case, eseguirlo e correggerlo finché non passa.
Leggere un risultato
| Risultato | Significato |
|---|---|
| Passed | Tutti i controlli sono passati. |
| Failed | Il target è stato eseguito e un controllo non è passato. Ogni controllo fallito dice cosa si aspettava e cosa ha trovato. |
| Error | Il test case non è mai arrivato ai suoi controlli: una fixture non si è lasciata inserire, un target ha il nome sbagliato, oppure il target ha lanciato un'eccezione quando il test case non se lo aspettava. Leggi prima l'errore. |
| Skipped | Il test case o la sua suite è disattivato. |
Cosa testare per primo
- I business event che proteggono i dati. Ogni regola che rifiuta un salvataggio o un'eliminazione riceve un test case che viene rifiutato e uno che viene consentito, così la regola non manca e non è nemmeno troppo severa.
- Gli script module che fanno calcoli. Date alcune righe, controlla i numeri che il modulo restituisce, e i casi limite: un giorno vuoto, un giorno pieno esattamente, un giorno oltre il limite.
- Le letture da cui dipende l'app. Un target business entity con un filtro, e un RowCount, dimostrano che una pagina mostra le righe che deve e nessuna di quelle che non deve.
- I flussi in più passi. Un target Script che fa ciò che farebbe un utente, e controlli in codice su tutto ciò che ne sarebbe dovuto seguire.
Test che restano utili
- Un test case, un'affermazione. Un test case che controlla dieci cose non dice quale delle dieci si è rotta.
- Dai al test case il nome della regola. "A zero estimate is refused" si legge come una specifica nell'elenco dei risultati.
- Controlla il comportamento, non la fixture. Contare le righe che hai appena inserito testa il caricamento delle fixture.
- Lega ogni fallimento al suo messaggio, come sopra.
- Tieni piccole le fixture. Solo le righe di cui l'affermazione ha bisogno, senza chiavi generate.
- Tieni i test con il codice. Una modifica e il test che la dimostra vanno nello stesso package, e nella stessa pull request.
Dove andare dopo
Archestack in VS Code spiega come eseguire i test dall'editor, Strategia di deployment la pipeline che li esegue prima di ogni deployment, e la pagina di riferimento i business event e gli script module di cui parla la maggior parte dei test.