Framework de Testes
O modulo wippy/test fornece um framework de testes estilo BDD com assercoes, hooks de ciclo de vida e mocking.
Configuracao
Adicione a dependencia:
wippy add wippy/test
wippy install
O modulo registra um comando test automaticamente. Uma vez instalado, wippy run test descobre e executa todas as entradas de teste no seu projeto.
Definindo Testes
Testes sao entradas function.lua com 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
Metadados do Teste
| Campo | Obrigatorio | Descricao |
|---|---|---|
type |
Sim | Deve ser "test" para que o runner o descubra |
suite |
Nao | Agrupa testes na saida do runner |
description |
Nao | Descricao legivel |
order |
Nao | Ordem de execucao dentro de uma suite (menor executa primeiro) |
Escrevendo Testes
Estilo BDD
Use blocos describe e it para estruturar testes:
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 Aninhadas
Suites podem ser aninhadas para organizacao:
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)
Pulando Testes
test.it_skip("not implemented yet", function()
test.fail("TODO")
end)
Testes pulados aparecem na saida mas nao contam como falhas.
Aliases de Suite
test.spec e test.context sao aliases para test.describe:
test.spec("feature", function()
test.context("when valid input", function()
test.it("succeeds", function()
test.ok(true)
end)
end)
end)
Assercoes
Igualdade
test.eq(actual, expected, msg?) -- actual == expected
test.neq(actual, expected, msg?) -- actual ~= expected
Veracidade
test.ok(val, msg?) -- val is truthy
test.fail(msg?) -- unconditional failure
Verificacoes de Nil
test.is_nil(val, msg?) -- val == nil
test.not_nil(val, msg?) -- val ~= nil
Verificacoes 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 e Colecoes
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
Comparacoes 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
Tratamento de Erros
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 as assercoes aceitam uma mensagem opcional como ultimo argumento. Em caso de falha, a mensagem e incluida na saida de erro.
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)
Hooks em suites aninhadas executam em ordem: before_each do pai executa antes do before_each do filho, e after_each do filho executa antes do after_each do pai.
Mocking
O sistema de mock substitui campos de objetos globais e os restaura automaticamente apos cada teste.
Mock 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
Caminhos de mock usam notacao de ponto: "process.send" substitui _G.process.send.
Mocks para process.send automaticamente fazem proxy de mensagens do framework de teste atraves da funcao original, para que o relato de eventos de teste continue funcionando quando process.send esta mockado.
Todos os mocks sao automaticamente restaurados apos cada teste via o hook after_each.
Executando Testes
Executar Todos os Testes
wippy run test
Filtrar por Padrao
wippy run test math
wippy run test user validation
Filtros correspondem a IDs de entradas. Multiplos padroes sao combinados.
Exemplo de Saida
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
Testes Simples
Para testes que nao precisam do framework BDD, defina uma funcao simples que retorna true ou lanca um erro:
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
O runner detecta se um teste usa eventos de caso BDD ou retorna um valor simples. Ambos os padroes funcionam com wippy run test.
Estrutura do Projeto
Um layout tipico de testes:
src/
_index.yaml
app.lua
test/
_index.yaml # test entries
math_test.lua
user_test.lua
integration_test.lua
O _index.yaml de testes define o namespace e as entradas de teste:
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 Infraestrutura
O runner de testes precisa de um process.host e terminal.host na sua aplicacao. Estes normalmente ja estao presentes. Se nao, adicione-os:
entries:
- name: processes
kind: process.host
lifecycle:
auto_start: true
- name: terminal
kind: terminal.host
lifecycle:
auto_start: true
Veja Tambem
- Visao Geral do Framework - Uso de modulos do framework
- Referencia CLI - Comandos CLI
- Funcoes - Registro de funcoes