Przewodnik - 12 min

Strategia testowania

Aplikację Archestack testuje się warstwami, od edytora, który zaznacza błąd w trakcie pisania, po pipeline, który niczego nie wdroży, dopóki któryś test nie przechodzi. Same testy są częścią aplikacji: znajdują się w jej packages, podróżują z nią do każdej przestrzeni roboczej i działają na własnej bazie sandbox, więc test nigdy nie czyta ani nie zmienia niczyich prawdziwych danych.

Pięć warstw

WarstwaCo wychwytujeKiedy działa
EdytorKod C# i kod stron, który się nie kompiluje, kolumnę lub Business Entity, które nie istnieją, Script Module wywołany z błędnym argumentemW trakcie pisania
Uruchomienia próbneSkrypt lub Business Event, który robi coś nie tak, wypróbowany przed zapisaniemPodczas pisania skryptu
Przypadki testoweZachowanie: regułę, którą egzekwuje Business Event, to, co zwraca Script Module, to, co odczytuje Business EntityPrzy każdej zmianie i przed każdym wdrożeniem
Sprawdzenia schematuZmianę tabeli, która traci dane albo przez którą typowany skrypt przestałby się kompilowaćPrzed każdą migracją i każdym wdrożeniem
Zestawy według harmonogramuWszystko, co zmieniło się wokół aplikacji: inny package, aktualizację, dane, których reguła się nie spodziewałaCo noc albo według dowolnego harmonogramu, w każdym środowisku

Dwie pierwsze nie wymagają pracy: edytor sprawdza kod tym samym kompilatorem i tym samym modelem danych, z którymi działa aplikacja, a przycisk Test Script Module i symulacja Business Event działają w trybie próbnym, w którym zapisy przez Db i Files są sprawdzane i rejestrowane, ale nigdy nie wykonywane, a maile i powiadomienia są rejestrowane, ale nie wysyłane. Wszystko inne, co skrypt robi sam, dzieje się naprawdę: wywołania HTTP, System.IO i wszystko, co robi pakiet NuGet, na przykład połączenie z inną bazą danych. Reszta tej strony dotyczy trzeciej warstwy, testów, które piszesz, i tego, jak uruchamiają je dwie pozostałe.

Przypadki testowe: given, when, then

Przypadek testowy to jedno twierdzenie o aplikacji, w trzech częściach: wiersze, które trzeba przygotować (given), jedna rzecz do uruchomienia (when) i to, co potem musi być prawdą (then). Przypadki są grupowane w zestawy. Ten, z aplikacji demo Service Planner, dowodzi, że tablica planowania oznacza dzień jako przepełniony tylko wtedy, gdy zaplanowane godziny technika przekraczają jego limit godzin:

{
  "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

Wiersze, których przypadek potrzebuje, tabela po tabeli, zapisywane od góry do dołu do pustego sandboxa. Wiersze dostają identyfikatory 1, 2, 3 w kolejności zapisu i to na nie wskazuje klucz obcy późniejszego fixture'a: powyżej technician_id 1 to Ana. Dlatego tabela, na którą wskazuje inny fixture, idzie pierwsza, a klucz generowany przez bazę danych jest pomijany, podobnie jak kolumny audytowe i każda kolumna, której wartość domyślna wystarczy. W builderze wybranie tabeli wypełnia przykładowe wiersze na podstawie nazw i typów jej kolumn, a klucz obcy jest listą wyboru spośród wierszy zapisywanych przez fixtures powyżej.

When

Dokładnie jeden cel, wskazany nazwą, nigdy identyfikatorem:

  • Script Module: uruchamia moduł z podanymi parametrami.
  • Business Entity: odczytuje encję, opcjonalnie z filtrem, żeby sprawdzić, co pokazałaby strona.
  • Business Event: odpala trigger na wierszu, we wskazanym momencie (BeforeCreate, AfterUpdate i tak dalej), tak jak zrobiłby to zapis.
  • Script: dowolny kod C# ze wszystkim, co ma Script Module, dla przypadku, który musi zrobić kilka rzeczy, zanim da się sprawdzić twierdzenie.

Then

Każde sprawdzenie to przedmiot sprawdzenia, operator i zwykle wartość oczekiwana. Przedmioty to Result (to, co zwrócił cel), Rows (wiersze odczytane potem z tabeli), Output (to, co cel zapisał w logu), Error, Duration i Code. Operatory to Equals, NotEquals, Contains, NotContains, GreaterThan, LessThan, IsNull, IsNotNull, Matches (wyrażenie regularne), RowCount, Succeeds i Fails. Ścieżka taka jak technicians[0].days[2].overbooked sięga w głąb wyniku, a ścieżka, która prowadzi donikąd, sprawia, że sprawdzenie kończy się niepowodzeniem, i mówi, gdzie się zatrzymała.

Testowanie, że coś zostaje odrzucone

Wiele reguł aplikacji mówi nie: Business Event odrzuca zapis, który by coś zepsuł. Przypadek dla takiej reguły próbuje zapisać i sprawdza, że zapis został odrzucony, z właściwego powodu, i że nic nie zostało zapisane:

// 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" }
]

Liczy się drugie sprawdzenie. Samo Fails przechodzi przy każdym błędzie, także przy literówce w nazwie kolumny, a to najczęstszy sposób, w jaki czerwony test po cichu przestaje cokolwiek testować. Powiąż niepowodzenie z jego komunikatem.

Sprawdzenia w kodzie

Sprawdzenie, którego nie da się wyrazić jednym przedmiotem sprawdzenia i jednym operatorem, jest sprawdzeniem w kodzie: kodem C#, który działa po celu w tym samym sandboxie, ze wszystkim, co ma Script Module, łącznie z typowanymi zbiorami w Db. Result, Output, Error, DurationMs i Failed przechowują wynik celu, a Assert, gdy sprawdzenie zawiedzie, mówi, czego oczekiwał i co zastał (Equal, NotEqual, True, False, Null, NotNull, Contains, Count i Fail). Sprawdzenie kończy się niepowodzeniem, gdy rzuci wyjątek, nie przejdzie Assert albo zwróci 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");

Sięgnij po nie, gdy sprawdzenie potrzebuje kilku wierszy naraz, obliczeń na nich albo porównania dwóch tabel. W repozytorium każde sprawdzenie w kodzie jest osobnym plikiem, <Case>.then.<n>.csx, edytowanym z tymi samymi funkcjami C# co każdy skrypt.

Sandbox

Test nigdy nie dotyka danych przestrzeni roboczej. Uruchomienie przekierowuje bazę danych z danymi do sandboxa, osobnej bazy danych tworzonej na żądanie na wzór prawdziwej, a każda tabela w nim jest opróżniana przed każdym przypadkiem, więc jedyne wiersze, które widzi przypadek, to jego własne fixtures i to, co zapisze jego cel. Definicje nadal pochodzą z przestrzeni roboczej, więc test sprawdza te Business Entities, Business Events i skrypty, które mają zaraz zostać wydane. Każda przestrzeń robocza ma cztery sandboxy, więc cztery uruchomienia mogą działać naraz; piąte czeka, aż któryś się zwolni.

  • Maile i powiadomienia są rejestrowane, a nie wysyłane. Wynik każdego przypadku wymienia, co jego skrypty wysłałyby mailem i kogo by powiadomiły, więc przypadek może sprawdzić także to.
  • Pliki zostają bez zmian. Zapisy, przeniesienia i usunięcia przez Files nigdy nie docierają do folderu workspace'u: przebieg zatrzymuje je dla siebie, więc odczyt właśnie zapisanego pliku działa.
  • Połączenia na zewnątrz są prawdziwe. Wszystko inne, co skrypt robi sam, dzieje się przy każdym uruchomieniu: żądanie HTTP, połączenie SMTP, które otwiera, System.IO i wszystko, co robi pakiet NuGet, na przykład połączenie z inną bazą danych, łącznie z zapisami. Trzymaj wywołania innych systemów z dala od tego, co uruchamiają testy, albo kieruj je na testowy endpoint lub testową bazę danych.
  • Ewidencja jest prawdziwa. Uruchomienie zapisanego przypadku odnotowuje wynik w przypadku i w zestawie oraz tworzy wpis w Event Log. Uruchomienie niezapisanego szkicu, przyciskiem Run w edytorze przypadku albo z VS Code, niczego nie odnotowuje.

Gdzie znajdują się testy

Test należy do package'a, który testuje. Podróżuje z tym package'em przez eksport, import albo Module Store, więc moduł trafia w nowe miejsce z dowodem, że nadal działa, a wszystko, na co wskazuje (Script Module, Business Entity, Business Event), wskazuje nazwą, więc trafia we właściwą rzecz w każdej przestrzeni roboczej. W repozytorium testy package'a są plikami: tests/<suite>/suite.json, jeden plik JSON na przypadek w cases/ i plik .csx obok przypadku dla celu Script albo sprawdzenia w kodzie. Przechodzą przegląd i scalanie tak jak kod, który testują.

Uruchamianie testów

  • W builderze strona Test Suite wymienia zestawy i ich przypadki i uruchamia przypadek, zestaw albo wszystko, z wartością oczekiwaną wobec rzeczywistej dla każdego nieudanego sprawdzenia. Run w edytorze przypadku wypróbowuje przypadek, zanim zostanie zapisany.
  • W VS Code Run Tests uruchamia przypadek, zestaw, package albo wszystkie packages na projekcie, po cztery przypadki naraz, i pokazuje każdy wynik w drzewie i w widoku Testing. Zobacz Testy w przewodniku po VS Code.
  • Według harmonogramu zestaw z cronem uruchamia się sam, w każdym środowisku, w którym jest zainstalowany: 0 0 2 * * ? to każdej nocy o drugiej. Każde uruchomienie tworzy jeden wpis w Event Log ze szczegółami każdego sprawdzenia, a jego ostatni wynik widać przy zestawie.
  • W pipeline'ie testy wszystkich packages działają na tymczasowym Archestack zbudowanym z repozytorium, zanim cokolwiek zostanie wdrożone, a jedno niepowodzenie zatrzymuje wdrożenie. Zobacz Strategia wdrożeń.
  • Z asystenta AI: MCP Server daje podłączonemu modelowi ten sam przewodnik po testach i narzędzia do napisania przypadku, uruchomienia go i poprawiania, aż przejdzie.

Jak czytać wynik

WynikZnaczenie
PassedKażde sprawdzenie przeszło.
FailedCel został uruchomiony, a jedno ze sprawdzeń nie przeszło. Każde nieudane sprawdzenie mówi, czego oczekiwało i co zastało.
ErrorPrzypadek nie doszedł do swoich sprawdzeń: fixture nie dał się wstawić, cel ma błędną nazwę albo cel rzucił wyjątek, choć przypadek się tego nie spodziewał. Najpierw przeczytaj błąd.
SkippedPrzypadek albo jego zestaw jest wyłączony.

Co testować najpierw

  1. Business Events, które chronią dane. Każda reguła, która odrzuca zapis albo usunięcie, dostaje przypadek, który zostaje odrzucony, i przypadek, który zostaje dopuszczony, więc wiadomo, że reguła istnieje i nie jest zbyt surowa.
  2. Script Modules, które liczą. Mając kilka wierszy, sprawdź liczby, które zwraca moduł, i przypadki brzegowe: pusty dzień, dzień wypełniony dokładnie, dzień przepełniony.
  3. Odczyty, od których zależy aplikacja. Cel Business Entity z filtrem i RowCount dowodzą, że strona pokazuje wiersze, które powinna, i żadnego, którego nie powinna.
  4. Przepływy z kilkoma krokami. Cel Script, który robi to, co zrobiłby użytkownik, i sprawdzenia w kodzie wszystkiego, co powinno z tego wyniknąć.

Testy, które pozostają przydatne

  • Jeden przypadek, jedno twierdzenie. Przypadek, który sprawdza dziesięć rzeczy, nie mówi, która z dziesięciu się zepsuła.
  • Nazwij przypadek od reguły. "A zero estimate is refused" czyta się na liście wyników jak specyfikację.
  • Sprawdzaj zachowanie, nie fixture. Liczenie wierszy, które właśnie wstawiłeś, testuje mechanizm ładowania fixtures.
  • Wiąż każde niepowodzenie z jego komunikatem, jak wyżej.
  • Utrzymuj małe fixtures. Tylko wiersze potrzebne twierdzeniu, bez generowanych kluczy.
  • Trzymaj testy razem z kodem. Zmiana i test, który jej dowodzi, należą do tego samego package'a i do tego samego pull requesta.

Co dalej

Strona Archestack w VS Code opisuje uruchamianie testów z edytora, Strategia wdrożeń pipeline, który uruchamia je przed każdym wdrożeniem, a strona Reference Business Events i Script Modules, których dotyczy większość testów.