Werkzeuge
Testing mit Antra
Dieses Kapitel zeigt, wie du heute in Antra kleine und mittlere Tests mit
Antra::Test schreibst.
Der Fokus ist bewusst praktisch:
- Wie sieht ein einzelner Test aus?
- Wie starte ich mehrere Tests?
- Wie benenne und gruppiere ich Tests lesbar?
- Wie pruefe ich Fehlerfaelle?
Die Kurzversion#
Wenn du nur die wichtigste Arbeitsregel willst:
- Fuer einzelne explizite Faelle nutze
Test.case(...)undTest.run_tests(...). - Fuer Package-basierte Tests nutze
@Test(...)undTest.run_test(...). - Verwende
*.t.antfuer ausfuehrbare Test-Runner. - Lege importierbare Test-Pakete ruhig unter
t/lib/...ab. - Gib Assertions nach Moeglichkeit eine klare Fehlermeldung mit.
Fuer viele Projekte reicht das bereits weit.
Ein erster kleiner Test#
Die kleinste Form ist ein expliziter Testfall mit Test.case(...).
import Antra::Test as Test
val cases = [
Test.case("eins plus eins", || -> Test.assert_eq(1 + 1, 2)),
Test.case("wahr bleibt wahr", || -> Test.assert_true(true))
]
Test.run_tests(cases)Die Idee ist einfach:
- jeder Test hat einen Namen
- jeder Test ist eine nullstellige Funktion
run_tests(...)fuehrt alle Faelle aus und druckt eine kleine TAP-artige Ausgabe
Wenn ein Test fehlschlaegt, wirft der Runner am Ende selbst einen Fehler. Damit eignet er sich gut fuer Skripte, CI und Shell-Aufrufe.
Die wichtigsten Assertions#
Antra::Test ist absichtlich klein. Die wichtigsten Helfer sind:
Test.assert_true(value)
Test.assert_true(value, message)
Test.assert_eq(actual, expected)
Test.assert_eq(actual, expected, message)
Test.assert_error(expr, needle)
Test.assert_error(expr, needle, message)assert_true#
Test.assert_true(len(users) > 0)
Test.assert_true(ready, "expected system to be ready")assert_eq#
Test.assert_eq(sum([1, 2, 3]), 6)
Test.assert_eq(user.role, :admin, "expected default role to be :admin")assert_error#
assert_error(...) prueft, dass ein Ausdruck wirft und dass die Fehlermeldung
einen bestimmten Text enthaelt.
Wichtig: das erste Argument ist jetzt ein on_use-Parameter. Du uebergibst
also direkt den Ausdruck, der spaeter innerhalb von assert_error(...)
ausgewertet werden soll.
on_use sorgt dafuer, dass der Ausdruck nicht schon vor dem Aufruf
ausgewertet wird — sonst wuerde parse("???").! bereits werfen, bevor
assert_error(...) ihn pruefen kann. Darum ist heute diese Form richtig:
Test.assert_error(parse("???").!, "unexpected token")und die alte Closure-Form ist fuer diesen Helfer nicht mehr noetig:
Test.assert_error(|| -> parse("???").!, "unexpected token")Was on_use genau ist (Semantik, moegliche Mehrfach-Auswertung, Regeln)
erklaert Kapitel 08 —
fuer assert_error(...) reicht: Ausdruck direkt uebergeben, der Helfer wertet
ihn kontrolliert aus.
Mit eigener Meldung:
Test.assert_error(
parse("???").!,
"unexpected token",
"expected parser to reject invalid syntax"
)Wenn du assert_error(...) in einer Pipe nutzen willst, beachte die normale
Pipe-Regel: der linke Ausdruck landet immer auf der ersten Argumentposition.
parse("???").! |> Test.assert_error("unexpected token")Das funktioniert hier, weil assert_error(...) den on_use-Parameter an erster
Stelle hat.
Warum eigene Fehlermeldungen oft sinnvoll sind#
Die Standardmeldungen sind fuer kleine Tests ok. In echten Test-Suites lohnt es sich aber oft, die Absicht explizit mitzuschreiben.
Statt nur:
Test.assert_eq(after.castling, ["k", "q"])ist das oft lesbarer:
Test.assert_eq(
after.castling,
["k", "q"],
"expected white castling rights to be cleared after white castles king-side"
)Wenn der Test spaeter fehlschlaegt, ist der Output meist deutlich schneller zu verstehen.
Von expliziten Cases zu Package-Tests#
Sobald mehrere Tests zusammengehoeren, ist die Package-Form meist angenehmer.
package Demo::Tests::MathTest {
import Antra::Test as Test
@Test("adds two numbers")
pub fn adds_numbers_test() -> {
Test.assert_eq(1 + 2, 3)
}
@Test("multiplies two numbers")
pub fn multiplies_numbers_test() -> {
Test.assert_eq(2 * 3, 6)
}
}Dann reicht ein Runner:
import Antra::Test as Test
import Demo::Tests::MathTest as MathTests
Test.run_test(MathTests)Wie run_test(...) Tests entdeckt#
run_test(...) entdeckt heute Tests nach einfachen Regeln:
- jede exportierte Funktion, deren Name auf
_testendet - jede exportierte Funktion mit
@Test(...) @Test("...")ueberschreibt den angezeigten Testnamen
Das erlaubt zwei Stile.
Nur ueber den Namen:
pub fn parses_config_test() -> ...Oder mit explizitem Label:
@Test("parses valid config files")
pub fn parses_config_test() -> ...Die zweite Form ist fuer Nutzer-Doku und CI-Output meist lesbarer.
@TestSuite(...) und group:#
Wenn du Tests grober strukturieren willst, kannst du Suite- und Gruppen- Metadaten setzen.
@TestSuite(name: "UCI")
package Chess::Tests::UCITest {
import Antra::Test as Test
@Test(name: "parses coordinate moves", group: :parse)
pub fn parses_coordinate_moves_test() -> {
...
}
@Test(name: "applies castling", group: :apply)
pub fn applies_castling_test() -> {
...
}
@Test(name: "rejects invalid move forms", group: :validation)
pub fn rejects_invalid_move_forms_test() -> {
...
}
}Das hat zwei Effekte:
- die Ausgabe kann nach Suite und Gruppe lesbarer gegliedert werden
- du kannst spaeter gezielt nur eine Gruppe laufen lassen
Zum Beispiel:
Test.run_test(UCITests, [group: :parse])Oder ueber mehrere Packages:
Test.run_test([SANTests, UCITests], [group: :validation, colorize: true])Ein typischer Projektaufbau#
Ein einfacher, heute gut funktionierender Aufbau ist:
t/
run.t.ant
lib/
MyApp/
Tests/
ParserTest.ant
EvalTest.antEin Runner kann dann so aussehen:
import Antra::Test as Test
import MyApp::Tests::ParserTest as ParserTests
import MyApp::Tests::EvalTest as EvalTests
Test.run_test([ParserTests, EvalTests], [colorize: true])Praktisch heisst das oft:
*.t.antsind ausfuehrbare Einstiegspunkte- importierbare Test-Sammlungen bleiben normale
.ant-Packages - Paketnamen wie
...::Tests::...machen im Editor und in Runnern schneller klar, was Produktionscode und was Testcode ist
Beispiel fuer einen kompletten Test-Runner#
import Antra::Test as Test
import Chess::Tests::SANTest as SANTests
import Chess::Tests::UCITest as UCITests
Test.run_test([SANTests, UCITests], [colorize: true])Mit Gruppenfilter:
Test.run_test([SANTests, UCITests], [group: :apply, colorize: true])Wie die Ausgabe ungefaehr aussieht#
Der Runner bleibt bewusst klein und TAP-nah.
Ein moeglicher Lauf sieht ungefaehr so aus:
1..3
# [SAN]
# Apply:
ok 1 - SAN applies castling from a castle-ready position
ok 2 - SAN applies a short legal move sequence
# Parse:
ok 3 - SAN parses common move forms
# 3 passed; 0 failedWichtig dabei:
- die Nummerierung laeuft global durch
- Suite- und Gruppenzeilen sind Zusatzstruktur, keine eigene Zaehlebene
- bei Fehlern kommt darunter noch die Detailmeldung des fehlgeschlagenen Tests
Wann run_tests(...) und wann run_test(...)?#
Als Faustregel:
run_tests(...)fuer kleine lokale Testsammlungen aus explizitencase(...)run_test(...)fuer Package-basierte Test-Suites mit@Test(...)
run_tests(...) ist gut, wenn du etwas sehr direkt auflisten willst.
run_test(...) ist besser, sobald Tests als normales Test-Package leben.
Was heute bewusst klein bleibt#
Antra::Test ist aktuell absichtlich kein grosses xUnit-Framework.
Was heute bewusst klein oder nicht vorhanden ist:
- kein
before_each - kein
after_each - keine tief verschachtelten
describe-Baeume - keine parallele Ausfuehrung
- keine riesige Assertion-Bibliothek
Das ist kein Versehen. Die aktuelle Surface ist eher fuer klare, nachvollziehbare Bibliotheks- und Sprachtests gedacht.
Praktische Empfehlungen#
Zum Schluss ein paar robuste Alltagsregeln:
- Gib Tests sprechende Namen, nicht nur technische Funktionsnamen.
- Nutze bei nichttrivialen Assertions eigene Fehlermeldungen.
- Gruppiere eher ueber Packages und
group:als ueber riesige Einzeldateien. - Halte Test-Runner klein; importiere lieber mehrere Test-Packages.
- Nutze
assert_error(...)bei Fehlerfaellen mit klaren Teilstrings statt nur "irgendein Fehler ist passiert" zu pruefen.
Wenn du so startest, bekommst du schon mit wenig Infrastruktur gut lesbare Tests in Antra.
