Teststrategie
Eine Archestack-App wird in Schichten getestet, vom Editor, der einen Fehler schon beim Tippen markiert, bis zur Pipeline, die nicht deployt, solange ein Test fehlschlägt. Die Tests selbst sind Teil der App: Sie liegen in ihren Packages, wandern mit ihr in jeden Workspace und laufen gegen eine eigene Sandbox-Datenbank, ein Test liest oder ändert also nie die echten Daten von irgendjemandem.
Fünf Schichten
| Schicht | Was sie abfängt | Wann sie läuft |
|---|---|---|
| Der Editor | C#- und Page-Code, der nicht kompiliert, eine Spalte oder Business Entity, die es nicht gibt, ein Script Module, das mit einem falschen Argument aufgerufen wird | Beim Tippen |
| Probeläufe | Ein Skript oder Business Event, das das Falsche tut, ausprobiert, bevor es gespeichert wird | Beim Schreiben eines Skripts |
| Testfälle | Verhalten: die Regel, die ein Business Event durchsetzt, was ein Script Module zurückgibt, was eine Business Entity liest | Bei jeder Änderung und vor jedem Deployment |
| Schemaprüfungen | Eine Tabellenänderung, bei der Daten verloren gehen oder nach der ein typisiertes Skript nicht mehr kompiliert | Vor jeder Migration und jedem Deployment |
| Suites nach Zeitplan | Alles, was sich rund um die App geändert hat: ein anderes Package, ein Upgrade, Daten, mit denen eine Regel nicht gerechnet hat | Jede Nacht oder nach einem beliebigen Zeitplan, in jeder Umgebung |
Die ersten beiden kosten keine Arbeit: Der Editor prüft gegen denselben Compiler und dasselbe
Datenmodell, mit denen die App läuft, und der Test-Button eines Script Modules
und die Simulation eines Business Events laufen als Probelauf, bei dem Schreibvorgänge über
Db und Files geprüft und aufgezeichnet, aber nie ausgeführt werden, und
Mails und Benachrichtigungen aufgezeichnet, aber nicht verschickt werden. Alles andere, was ein
Skript selbst tut, ist echt: HTTP-Aufrufe, System.IO und alles, was ein
NuGet-Paket tut, etwa eine Verbindung zu einer anderen
Datenbank. Der Rest dieser Seite handelt von der
dritten Schicht, den Tests, die du schreibst, und davon, wie die anderen beiden sie ausführen.
Testfälle: Given, When, Then
Ein Testfall ist eine einzelne Aussage über die App, in drei Teilen: die Zeilen, die bereitgelegt werden (given), das eine, was ausgeführt wird (when), und was danach gelten muss (then). Testfälle werden in Suites gruppiert. Dieser hier, aus der Demo Service Planner, belegt, dass das Planungsboard einen Tag nur dann als überbucht markiert, wenn die geplanten Stunden eines Technikers über seiner Kapazität liegen:
{
"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
Die Zeilen, die der Testfall braucht, Tabelle für Tabelle, von oben nach unten in eine leere
Sandbox geschrieben. Die Zeilen bekommen die IDs 1, 2, 3 in der Reihenfolge, in der sie
geschrieben werden, und darauf zeigt der Fremdschlüssel einer späteren Fixture: Oben ist
technician_id 1 Ana. Eine Tabelle, auf die eine andere Fixture zeigt, kommt also
zuerst, und ein Schlüssel, den die Datenbank erzeugt, wird weggelassen, ebenso die Audit-Spalten
und jede Spalte, deren Default genügt. Im Builder füllt die Auswahl einer Tabelle Beispielzeilen
aus den Namen und Typen ihrer Spalten aus, und ein Fremdschlüssel ist eine Auswahl aus den Zeilen,
die die Fixtures darüber schreiben.
When
Genau ein Ziel, über seinen Namen angegeben, nie über eine ID:
- Script Module: führt ein Modul mit den angegebenen Parametern aus.
- Business Entity: liest eine Entity zurück, optional gefiltert, um zu prüfen, was eine Page zeigen würde.
- Business Event: feuert einen Trigger auf einer Zeile, zum genannten Zeitpunkt (BeforeCreate, AfterUpdate und so weiter), so wie es ein Speichern tun würde.
- Script: freies C# mit allem, was ein Script Module hat, für einen Testfall, der mehrere Dinge tun muss, bevor sich die Aussage prüfen lässt.
Then
Jede Prüfung besteht aus einem Subjekt, einem Operator und meist einem erwarteten Wert. Die
Subjekte sind Result (was das Ziel zurückgegeben hat), Rows (Zeilen, die danach
aus einer Tabelle zurückgelesen werden), Output (was das Ziel geloggt hat),
Error, Duration und Code. Die Operatoren sind Equals, NotEquals,
Contains, NotContains, GreaterThan, LessThan, IsNull, IsNotNull, Matches (ein regulärer Ausdruck),
RowCount, Succeeds und Fails. Ein Pfad wie technicians[0].days[2].overbooked geht in
ein Ergebnis hinein, und ein Pfad, der ins Leere führt, lässt die Prüfung fehlschlagen und sagt,
wo er stehen geblieben ist.
Testen, dass etwas abgelehnt wird
Viele Regeln einer App sagen Nein: Ein Business Event lehnt ein Speichern ab, das etwas kaputt machen würde. Der Testfall für eine solche Regel versucht das Speichern und prüft, dass es abgelehnt wurde, aus dem richtigen Grund, und dass nichts geschrieben wurde:
// 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" }
] Die zweite Prüfung ist die, auf die es ankommt. Fails allein besteht bei jedem beliebigen Fehler, auch bei einer falsch geschriebenen Spalte, und das ist der häufigste Weg, auf dem ein roter Test unbemerkt aufhört, überhaupt etwas zu testen. Binde den Fehlschlag an seine Meldung.
Code-Prüfungen
Eine Prüfung, die sich nicht mit einem Subjekt und einem Operator ausdrücken lässt, ist eine
Code-Prüfung: C#, das nach dem Ziel in derselben Sandbox läuft, mit allem, was
ein Script Module hat, einschließlich der typisierten Sets auf Db.
Result, Output, Error, DurationMs und
Failed enthalten das Ergebnis des Ziels, und Assert sagt, was es erwartet
und was es vorgefunden hat, wenn eine Prüfung fehlschlägt (Equal, NotEqual, True, False, Null,
NotNull, Contains, Count und Fail). Die Prüfung schlägt fehl, wenn sie eine Exception wirft, ein
Assert nicht besteht oder false zurückgibt.
// 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");
Greif zu einer, wenn eine Prüfung mehrere Zeilen auf einmal braucht, Rechnungen darüber oder einen
Vergleich zwischen zwei Tabellen. In einem Repository ist jede Code-Prüfung eine eigene Datei,
<Case>.then.<n>.csx, die du mit denselben C#-Funktionen bearbeitest wie
jedes Skript.
Die Sandbox
Ein Test berührt nie die Daten des Workspaces. Ein Lauf leitet die Datenbank mit den Daten auf eine Sandbox um, eine eigene Datenbank, die bei Bedarf in der Form der echten angelegt wird, und jede Tabelle darin wird vor jedem Testfall geleert, sodass ein Testfall nur seine eigenen Fixtures sieht und das, was sein Ziel schreibt. Die Definitionen kommen weiterhin aus dem Workspace, ein Test prüft also die Business Entities, Business Events und Skripte, die gleich ausgeliefert werden. Jeder Workspace hat vier Sandboxes, es können also vier Läufe gleichzeitig laufen; ein fünfter wartet, bis eine frei wird.
- Mails und Benachrichtigungen werden aufgezeichnet, nicht verschickt. Das Ergebnis jedes Testfalls listet, was seine Skripte gemailt hätten und wen sie benachrichtigt hätten, sodass ein Testfall auch das prüfen kann.
- Dateien bleiben, wie sie sind. Schreib-, Verschiebe- und Löschvorgänge über
Fileserreichen den Ordner des Workspaces nie: Der Lauf behält sie für sich, sodass eine gerade geschriebene Datei wieder gelesen werden kann. - Ausgehende Aufrufe sind echt. Alles andere, was ein Skript selbst tut, passiert bei jedem Lauf: eine HTTP-Anfrage, eine SMTP-Verbindung, die es öffnet,
System.IOund alles, was ein NuGet-Paket tut, etwa eine Verbindung zu einer anderen Datenbank, Schreibvorgänge eingeschlossen. Halte Aufrufe an andere Systeme aus dem heraus, was die Tests ausführen, oder richte sie auf einen Test-Endpunkt oder eine Testdatenbank. - Die Buchführung ist echt. Der Lauf eines gespeicherten Testfalls hält sein Ergebnis am Testfall und an der Suite fest und schreibt einen Eintrag ins Event Log. Der Lauf eines ungespeicherten Entwurfs, über den Run-Button des Testfall-Editors oder aus VS Code, hält nichts fest.
Wo Tests liegen
Ein Test gehört zu dem Package, das er testet. Er wandert mit diesem Package durch einen Export,
einen Import oder den Module Store, ein Modul kommt also mit dem Beweis, dass es noch funktioniert,
an einem neuen Ort an, und alles, was er nennt (das Script Module, die Business Entity, das
Business Event), nennt er beim Namen, sodass er in jedem Workspace auf das Richtige zeigt. In einem
Repository sind die Tests eines Packages Dateien: tests/<suite>/suite.json, eine
JSON-Datei pro Testfall unter cases/ und eine .csx neben einem Testfall
für ein Script-Ziel oder eine Code-Prüfung. Sie werden reviewt und gemergt wie der Code, den sie
testen.
Tests ausführen
- Im Builder listet die Seite Test Suite die Suites und ihre Testfälle und führt einen Testfall, eine Suite oder alles aus, mit Erwartung gegen Ist-Wert für jede fehlgeschlagene Prüfung. Run im Testfall-Editor probiert den Testfall aus, bevor er gespeichert ist.
- In VS Code führt Run Tests einen Testfall, eine Suite, ein Package oder alle Packages gegen das Projekt aus, vier Testfälle gleichzeitig, und zeigt jedes Ergebnis im Baum und in der Ansicht Testing. Siehe Tests in der VS Code-Anleitung.
- Nach Zeitplan läuft eine Suite mit einem Cron von selbst, in jeder Umgebung, in
der sie installiert ist:
0 0 2 * * ?ist jede Nacht um zwei. Jeder Lauf schreibt einen Eintrag ins Event Log mit den Details jeder Prüfung, und das letzte Ergebnis steht an der Suite. - In der Pipeline laufen die Tests jedes Packages gegen ein Wegwerf-Archestack, das aus dem Repository gebaut wird, bevor irgendetwas deployt wird, und ein einziger Fehlschlag stoppt das Deployment. Siehe Deployment-Strategie.
- Aus einem KI-Assistenten heraus gibt der MCP Server einem verbundenen Modell denselben Test-Guide und die Tools, um einen Testfall zu schreiben, auszuführen und so lange zu korrigieren, bis er besteht.
Ein Ergebnis lesen
| Ergebnis | Bedeutet |
|---|---|
| Passed | Jede Prüfung ist erfüllt. |
| Failed | Das Ziel ist gelaufen, und eine Prüfung ist nicht erfüllt. Jede fehlgeschlagene Prüfung sagt, was sie erwartet und was sie vorgefunden hat. |
| Error | Der Testfall ist nie bis zu seinen Prüfungen gekommen: Eine Fixture ließ sich nicht einfügen, ein Ziel ist falsch benannt, oder das Ziel hat eine Exception geworfen, obwohl der Testfall das nicht erwartet hat. Lies zuerst den Fehler. |
| Skipped | Der Testfall oder seine Suite ist deaktiviert. |
Was du zuerst testen solltest
- Die Business Events, die Daten schützen. Jede Regel, die ein Speichern oder ein Löschen ablehnt, bekommt einen Testfall, der abgelehnt wird, und einen, der erlaubt wird, sodass die Regel weder fehlt noch zu streng ist.
- Script Modules, die rechnen. Prüfe bei ein paar gegebenen Zeilen die Zahlen, die das Modul zurückgibt, und die Grenzfälle: einen leeren Tag, einen genau gefüllten Tag, einen Tag darüber.
- Die Lesezugriffe, auf die die App angewiesen ist. Ein Business-Entity-Ziel mit einem Filter und ein RowCount belegen, dass eine Page die Zeilen zeigt, die sie zeigen soll, und keine, die sie nicht zeigen soll.
- Abläufe über mehrere Schritte. Ein Script-Ziel, das tut, was ein Benutzer tun würde, und Code-Prüfungen für alles, was daraus hätte folgen sollen.
Tests, die nützlich bleiben
- Ein Testfall, eine Aussage. Ein Testfall, der zehn Dinge prüft, sagt nichts darüber, welches der zehn kaputtgegangen ist.
- Benenne den Testfall nach der Regel. "A zero estimate is refused" liest sich in der Liste der Ergebnisse wie eine Spezifikation.
- Prüfe Verhalten, nicht die Fixture. Wer die Zeilen zählt, die er gerade eingefügt hat, testet den Fixture-Loader.
- Binde jeden Fehlschlag an seine Meldung, wie oben.
- Halte Fixtures klein. Nur die Zeilen, die die Aussage braucht, ohne erzeugte Schlüssel.
- Halte Tests beim Code. Eine Änderung und der Test, der sie belegt, gehören in dasselbe Package und in denselben Pull Request.
Wie es weitergeht
Archestack in VS Code behandelt das Ausführen von Tests aus dem Editor, Deployment-Strategie die Pipeline, die sie vor jedem Deployment ausführt, und die Reference die Business Events und Script Modules, um die es in den meisten Tests geht.