Framework de Testing
El modulo wippy/test proporciona un framework de testing estilo BDD con aserciones, hooks de ciclo de vida y mocking.
Configuracion
Agrega la dependencia:
wippy add wippy/test
wippy install
El modulo registra un comando test automaticamente. Una vez instalado, wippy run test descubre y ejecuta todas las entradas de test en tu proyecto.
Definir Tests
Los tests son entradas function.lua con meta.type: test:
version: "1.0"
namespace: app.test
entries:
- name: math
kind: function.lua
meta:
type: test
suite: math
description: Math operations
source: file://math_test.lua
method: run
imports:
test: wippy.test:test
Metadatos del Test
| Field | Required | Description |
|---|---|---|
type |
Yes | Debe ser "test" para que el runner lo descubra |
suite |
No | Agrupa tests en la salida del runner |
description |
No | Descripcion legible |
order |
No | Orden dentro de una suite (menor se ejecuta primero) |
Escribir Tests
Estilo BDD
Usa bloques describe e it para estructurar tests:
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 }
Suites Anidadas
Las suites pueden anidarse para organizacion:
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)
Omitir Tests
test.it_skip("not implemented yet", function()
test.fail("TODO")
end)
Los tests omitidos aparecen en la salida pero no cuentan como fallos.
Alias de Suites
test.spec y test.context son alias de test.describe:
test.spec("feature", function()
test.context("when valid input", function()
test.it("succeeds", function()
test.ok(true)
end)
end)
end)
Aserciones
Igualdad
test.eq(actual, expected, msg?) -- actual == expected
test.neq(actual, expected, msg?) -- actual ~= expected
Veracidad
test.ok(val, msg?) -- val is truthy
test.fail(msg?) -- unconditional failure
Verificaciones de Nil
test.is_nil(val, msg?) -- val == nil
test.not_nil(val, msg?) -- val ~= nil
Verificaciones de Tipo
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 y Colecciones
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
Comparaciones Numericas
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
Manejo de Errores
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
Todas las aserciones aceptan un mensaje opcional como ultimo argumento. En caso de fallo, el mensaje se incluye en la salida de error.
Hooks de Ciclo de Vida
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)
Los hooks en suites anidadas se ejecutan en orden: el before_each del padre se ejecuta antes del before_each del hijo, y el after_each del hijo se ejecuta antes del after_each del padre.
Mocking
El sistema de mock reemplaza campos de objetos globales y los restaura automaticamente despues de cada test.
Mocking Basico
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)
API de Mock
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
Las rutas de mock usan notacion de punto: "process.send" reemplaza _G.process.send.
Los mocks para process.send redirigen automaticamente los mensajes del framework de testing a traves de la funcion original, para que el reporte de eventos de test continue funcionando cuando process.send esta mockeado.
Todos los mocks se restauran automaticamente despues de cada test mediante el hook after_each.
Ejecutar Tests
Ejecutar Todos los Tests
wippy run test
Filtrar por Patron
wippy run test math
wippy run test user validation
Los filtros coinciden contra los IDs de las entradas. Multiples patrones se combinan.
Ejemplo de Salida
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
Tests Simples
Para tests que no necesitan el framework BDD, define una funcion simple que retorne true o lance un error:
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
El runner detecta si un test usa eventos de casos BDD o retorna un valor simple. Ambos patrones funcionan con wippy run test.
Estructura del Proyecto
Un layout tipico de tests:
src/
_index.yaml
app.lua
test/
_index.yaml # test entries
math_test.lua
user_test.lua
integration_test.lua
El _index.yaml de tests define el namespace y las entradas de test:
version: "1.0"
namespace: app.test
entries:
- name: math
kind: function.lua
meta:
type: test
suite: math
source: file://math_test.lua
method: run
imports:
test: wippy.test:test
- name: user
kind: function.lua
meta:
type: test
suite: user
source: file://user_test.lua
method: run
imports:
test: wippy.test:test
Requisitos de Infraestructura
El runner de tests necesita un process.host y un terminal.host en tu aplicacion. Estos tipicamente ya estan presentes. Si no, agregalos:
entries:
- name: processes
kind: process.host
lifecycle:
auto_start: true
- name: terminal
kind: terminal.host
lifecycle:
auto_start: true
Ver Tambien
- Descripcion General del Framework - Uso de modulos del framework
- Referencia CLI - Comandos CLI
- Funciones - Registro de funciones