Test-Framework
Das Modul wippy/test stellt BDD-Suites, Assertions, Lifecycle-Hooks, Mocks und
einen Runner für Testeinträge bereit.
Diese Seite ist eine API-Einführung. Die Lua-, YAML-, Ausgabe- und Projektstrukturblöcke
sind Referenz-Snippets für ein bestehendes Wippy-Projekt und ergeben zusammen kein
direkt kopierbares Projekt. Namen wie validate, format_name, db, connect und
notify_user stehen für Funktionen oder Module der getesteten Anwendung. Ein
vollständiges ausführbares Beispiel finden Sie unter
Eine Wippy-Anwendung testen.
Einrichtung
Fügen Sie die Abhängigkeit hinzu:
wippy add wippy/test
wippy install
Das Modul registriert den Test-Einstiegspunkt automatisch als Befehl mit
use_case: test. Nach der Installation entdeckt und führt wippy test alle
Testeinträge im Projekt aus.
Tests definieren
Tests sind function.lua-Einträge mit meta.type: test:
version: "1.0"
namespace: app.test
entries:
- name: math
kind: function.lua
meta:
type: test
suite: math
name: Math operations
source: file://math_test.lua
method: main
imports:
test: wippy.test:test
Test-Metadaten
| Feld | Erforderlich | Beschreibung |
|---|---|---|
type |
Ja | Muss "test" sein, damit der Runner den Test entdeckt |
suite |
Nein | Gruppiert Tests in der Runner-Ausgabe |
name |
Nein | Anzeigename in der Runner-Ausgabe |
order |
Nein | Sortierreihenfolge innerhalb einer Suite (niedrigere Werte zuerst) |
Tests schreiben
BDD-Stil
Strukturieren Sie Tests mit describe- und it-Blöcken:
local test = require("test")
local function define_tests()
test.describe("calculator", function()
test.it("adds numbers", function()
test.eq(1 + 1, 2)
end)
test.it("multiplies numbers", function()
test.eq(3 * 4, 12)
end)
end)
end
local run_cases = test.run_cases(define_tests)
local function run(options)
local result = run_cases(options)
if result.failed_tests > 0 then
error("tests failed: " .. result.failed_tests)
end
return result
end
return { run = run }
Verschachtelte Suites
Suites können verschachtelt werden, um zusammengehöriges Verhalten zu gruppieren:
test.describe("user", function()
test.describe("validation", function()
test.it("requires name", function()
test.ok(validate({}).error)
end)
test.it("accepts valid input", function()
test.is_nil(validate({name = "Alice"}).error)
end)
end)
test.describe("formatting", function()
test.it("formats display name", function()
test.eq(format_name("alice"), "Alice")
end)
end)
end)
Tests überspringen
test.it_skip("not implemented yet", function()
test.fail("TODO")
end)
Übersprungene Tests erscheinen in der Ausgabe, zählen aber nicht als Fehler.
Suite-Aliase
test.spec und test.context sind Aliase für test.describe:
test.spec("feature", function()
test.context("when valid input", function()
test.it("succeeds", function()
test.ok(true)
end)
end)
end)
Assertions
Gleichheit
test.eq(actual, expected, msg?) -- actual == expected
test.neq(actual, expected, msg?) -- actual ~= expected
Wahrheitswerte
test.ok(val, msg?) -- val is truthy
test.fail(msg?) -- unconditional failure
Nil-Prüfungen
test.is_nil(val, msg?) -- val == nil
test.not_nil(val, msg?) -- val ~= nil
Typprüfungen
test.is_true(val, msg?) -- val == true
test.is_false(val, msg?) -- val == false
test.is_string(val, msg?)
test.is_number(val, msg?)
test.is_table(val, msg?)
test.is_function(val, msg?)
test.is_boolean(val, msg?)
Strings und Collections
test.contains(str, substr, msg?) -- substring match
test.matches(str, pattern, msg?) -- Lua pattern match
test.has_key(tbl, key, msg?) -- table key exists
test.len(val, expected, msg?) -- #val == expected
Numerische Vergleiche
test.gt(a, b, msg?) -- a > b
test.gte(a, b, msg?) -- a >= b
test.lt(a, b, msg?) -- a < b
test.lte(a, b, msg?) -- a <= b
Fehlerbehandlung
test.throws(fn, msg?) -- fn() raises error, returns it
test.has_error(val, err, msg?) -- val is nil, err is not nil
test.no_error(val, err, msg?) -- err is nil
Alle Assertions akzeptieren eine optionale Nachricht als letztes Argument. Bei einem Fehlschlag wird die Nachricht in der Fehlerausgabe angezeigt.
Lifecycle-Hooks
test.describe("database", function()
test.before_all(function()
-- runs once before the suite
db = connect()
end)
test.after_all(function()
-- runs once after the suite
db:close()
end)
test.before_each(function()
-- runs before each test
db:begin_transaction()
end)
test.after_each(function()
-- runs after each test
db:rollback()
end)
test.it("inserts a record", function()
db:exec("INSERT INTO users (name) VALUES ('Alice')")
local count = db:query_row("SELECT COUNT(*) FROM users")
test.eq(count, 1)
end)
end)
Hooks verschachtelter Suites werden geordnet ausgeführt: Das before_each der
übergeordneten Suite läuft vor dem before_each der untergeordneten Suite; deren
after_each läuft vor dem after_each der übergeordneten Suite.
Mocking
Das Mock-System ersetzt globale Objektfelder und stellt sie nach jedem Test automatisch wieder her.
Einfaches Mocking
test.describe("notifications", function()
test.it("sends message", function()
local sent = false
test.mock("process.send", function(pid, topic, payload)
sent = true
end)
notify_user("hello")
test.is_true(sent)
-- mock is auto-restored after this test
end)
end)
Mock-API
test.mock("object.field", replacement) -- replace a global field
test.mock_process("field", replacement) -- shorthand for process fields
test.restore_mock("object.field") -- restore one mock
test.restore_all_mocks() -- restore all mocks
Mock-Pfade verwenden Punkt-Notation: "process.send" ersetzt _G.process.send.
Mocks für process.send leiten Nachrichten des Testframeworks automatisch über die
Originalfunktion weiter. Die Berichterstattung über Testereignisse funktioniert daher
weiterhin, wenn process.send gemockt ist.
Alle Mocks werden nach jedem Test automatisch über den Hook after_each wiederhergestellt.
Tests ausführen
Alle Tests ausführen
wippy test
Nach Muster filtern
wippy test math
wippy test user validation
Filter suchen nach wörtlichen Teilstrings in den Eintrags-IDs. Bei mehreren Mustern wird ein Eintrag ausgeführt, wenn seine ID mindestens eines davon enthält.
Beispielausgabe
3 tests in 1 suites
calculator
+ adds numbers 0ms
+ multiplies numbers 0ms
- divides by zero 1ms
Error: expected error, got nil
1 suite | 2 passed | 1 failed | 0 skipped | 3ms
Einfache Tests
Für Tests ohne BDD-Suites definieren Sie eine Funktion, die true zurückgibt oder
einen Fehler auslöst:
local funcs = require("funcs")
local function main()
local result, err = funcs.call("app:my_function", "input")
if err then
error("call failed: " .. tostring(err))
end
if result ~= "expected" then
error("expected 'expected', got: " .. tostring(result))
end
return true
end
return { main = main }
- name: integration
kind: function.lua
meta:
type: test
suite: integration
source: file://integration_test.lua
method: main
modules:
- funcs
Der Runner erkennt, ob ein Test BDD-Fallereignisse verwendet oder einen einfachen Wert
zurückgibt. Beide Muster funktionieren mit wippy test.
Projektstruktur
Ein typisches Test-Layout:
src/
_index.yaml
app.lua
test/
_index.yaml # test entries
math_test.lua
user_test.lua
integration_test.lua
Die Testdatei _index.yaml definiert Namespace und Einträge:
version: "1.0"
namespace: app.test
entries:
- name: math
kind: function.lua
meta:
type: test
suite: math
source: file://math_test.lua
method: main
imports:
test: wippy.test:test
- name: user
kind: function.lua
meta:
type: test
suite: user
source: file://user_test.lua
method: main
imports:
test: wippy.test:test
Terminal-Host
wippy/test hängt von wippy/terminal ab. Dieses Modul stellt den automatisch
startenden Host wippy.terminal:host bereit, den der CLI-Runner verwendet. Anwendungen
müssen nicht eigens einen Prozess- oder Terminal-Host deklarieren, nur um wippy test
auszuführen.
Siehe auch
- Framework-Übersicht — Framework-Module installieren und importieren
- CLI-Referenz — Testbefehl und Optionen
- Funktionen — Funktionseinträge und Aufrufe