Nuevo docs/testing.md con convenciones de test por lenguaje (Go, Python, Bash con 3 opciones), tablas unit_tests y e2e_tests, consultas FTS5 de ejemplo. Actualiza functions.md y CLAUDE.md con referencia a unit_tests.
7.4 KiB
Testing
El registry tiene dos niveles de tests:
- Unit tests (
unit_testsenregistry.db) — tests individuales extraidos automaticamente de los archivos de test de cada funcion. - E2E tests (
e2e_testsenoperations.db) — tests de integracion que verifican como las funciones se componen dentro de una app.
Unit tests
fn index lee cada archivo de test referenciado por test_file_path en las funciones testeadas, extrae los test cases individuales con su codigo, y los inserta en la tabla unit_tests.
Tabla unit_tests
| Campo | Tipo | Descripcion |
|---|---|---|
id |
string | {function_id}_t{n} (ej: filter_slice_go_core_t0) |
function_id |
string | FK a functions.id |
name |
string | Nombre del test case |
code |
string | Codigo fuente completo del test |
file_path |
string | Ruta relativa al archivo de test |
lang |
string | Lenguaje (go, py, bash) |
created_at |
datetime | Fecha de indexacion |
updated_at |
datetime | Fecha de ultima indexacion |
FTS5 disponible sobre id, name, code, function_id, lang.
Consultas utiles
# Todos los tests de una funcion
sqlite3 registry.db "SELECT id, name FROM unit_tests WHERE function_id = 'filter_slice_go_core';"
# Buscar tests por contenido (FTS5)
sqlite3 registry.db "SELECT id, function_id, name FROM unit_tests WHERE id IN (SELECT id FROM unit_tests_fts WHERE unit_tests_fts MATCH 'retry') LIMIT 10;"
# Tests por lenguaje
sqlite3 registry.db "SELECT lang, COUNT(*) FROM unit_tests GROUP BY lang;"
# Ver codigo de un test
sqlite3 registry.db "SELECT code FROM unit_tests WHERE id = 'cache_decorator_py_core_t0';"
Convenciones de test por lenguaje
El parser automatico de fn index detecta test cases segun el lenguaje. Para que los tests se extraigan correctamente, seguir estas convenciones.
Go
Convencion estandar de Go. El parser detecta funciones func TestXxx(t *testing.T):
func TestFilterSlice(t *testing.T) {
t.Run("filtra pares", func(t *testing.T) {
got := FilterSlice([]int{1, 2, 3, 4, 5}, func(n int) bool { return n%2 == 0 })
if len(got) != 2 || got[0] != 2 || got[1] != 4 {
t.Errorf("got %v, want [2 4]", got)
}
})
t.Run("slice vacio retorna vacio", func(t *testing.T) {
got := FilterSlice([]int{}, func(n int) bool { return true })
if len(got) != 0 {
t.Errorf("got %v, want []", got)
}
})
}
Deteccion: ^func (Test\w+)\s*\( — cada func Test... es un test case. Los subtests (t.Run) se incluyen dentro del codigo del test padre.
Archivo: {domain}/{name}_test.go (convencion Go estandar).
Python
Convencion estandar de pytest. El parser detecta funciones def test_xxx(:
def test_funcion_llamada_una_vez(store):
calls = []
@cache_decorator(store, ttl=60)
def compute(x: int) -> int:
calls.append(x)
return x * 10
assert compute(5) == 50
assert compute(5) == 50
assert len(calls) == 1
def test_ttl_expirado(store):
# ...
Deteccion: ^def (test_\w+)\s*\( — cada funcion top-level def test_... es un test case. El codigo incluye todo hasta la siguiente def test_ o fin de archivo.
Archivo: {domain}/{name}_test.py.
Bash
Bash no tiene framework estandar de testing. El parser soporta tres convenciones, en orden de prioridad:
Opcion 1: funciones test_xxx() (preferida)
La mas explicita y la que mejor se parsea:
#!/usr/bin/env bash
source "$(dirname "$0")/mi_funcion.sh"
PASS=0; FAIL=0
assert_eq() {
local name="$1" got="$2" want="$3"
if [ "$got" = "$want" ]; then echo " PASS: $name"; ((PASS++))
else echo " FAIL: $name (got='$got', want='$want')"; ((FAIL++)); fi
}
test_caso_basico() {
local got
got=$(mi_funcion "input")
assert_eq "caso basico" "$got" "expected"
}
test_caso_vacio() {
local got
got=$(mi_funcion "")
assert_eq "input vacio" "$got" ""
}
# Ejecutar todos los tests
test_caso_basico
test_caso_vacio
echo "Resultados: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ] || exit 1
Deteccion: ^(test_\w+)\s*\(\)\s*\{ — cada funcion test_xxx() { ... } es un test case.
Opcion 2: secciones === nombre ===
Para tests que agrupan multiples asserts bajo secciones nombradas:
#!/usr/bin/env bash
source "$(dirname "$0")/mi_funcion.sh"
echo "=== caso basico ==="
got=$(mi_funcion "input")
assert_eq "retorna expected" "$got" "expected"
echo "=== caso edge ==="
got=$(mi_funcion "")
assert_eq "input vacio" "$got" ""
echo "=== errores ==="
assert_fail "input invalido" mi_funcion "--bad"
Deteccion: ^(echo\s+["'])?===\s*(\w[\w\s]*\w)\s*===(["'])?\s*$ — cada linea con === nombre === (con o sin echo) abre una seccion. El nombre debe contener al menos dos caracteres alfanumericos (las lineas de separacion puras como ====== se ignoran).
Opcion 3: comentarios # Test:
Para scripts simples donde cada test se marca con un comentario:
#!/usr/bin/env bash
source "$(dirname "$0")/mi_funcion.sh"
# Test: caso basico
got=$(mi_funcion "input")
[ "$got" = "expected" ] || { echo "FAIL"; exit 1; }
# Test: input vacio
got=$(mi_funcion "")
[ "$got" = "" ] || { echo "FAIL"; exit 1; }
Deteccion: ^#\s*[Tt]est:\s*(.+) — cada comentario # Test: nombre abre un bloque.
Recomendacion
Usar opcion 1 (funciones test_xxx()) para tests nuevos. Es la mas explicita, cada test esta aislado en su propia funcion, y se parsea sin ambiguedad.
La opcion 2 (secciones ===) es aceptable cuando ya existe el patron en el archivo (como pass_test.sh).
Archivo: {domain}/{name}_test.sh.
E2E tests
Los e2e tests viven en operations.db de cada app. No se extraen automaticamente — se crean manualmente o por el bucle reactivo cuando se necesita verificar que un flujo end-to-end funciona.
Tabla e2e_tests
| Campo | Tipo | Descripcion |
|---|---|---|
id |
string | Identificador unico |
name |
string | Nombre descriptivo del test |
description |
string | Que verifica este test |
relation_id |
string | FK a relations.id — que pipeline/relacion prueba |
steps |
[]string | Funciones involucradas en orden |
input_fixture |
JSON | Datos de entrada para el test |
expected |
JSON | Resultado esperado |
last_status |
string | pass, fail, skip, o vacio |
last_run_at |
datetime | Ultima ejecucion |
execution_id |
string | Referencia a la ejecucion que lo corrio |
duration_ms |
int | Duracion en milisegundos |
created_at |
datetime | Fecha de creacion |
updated_at |
datetime | Ultima actualizacion |
FTS5 disponible sobre id, name, description, steps.
Diferencia con assertions
| Assertions | E2E tests | |
|---|---|---|
| Que son | Reglas declarativas sobre datos | Ejecuciones concretas de flujos |
| Cuando corren | En cada ejecucion del bucle reactivo | Bajo demanda o en CI |
| Sobre que | Una entity (precio > 0) |
Un flujo completo (input → pipeline → output) |
| Resultado | pass/fail sobre el valor actual | pass/fail comparando output vs expected |
Ejemplo de uso
# Crear un e2e test para un pipeline
fn ops e2e add --name "metabase_setup_completo" \
--relation-id "rel_setup_metabase" \
--steps '["docker_pull_image_go_infra","init_metabase_go_pipelines"]' \
--input '{"project":"test"}' \
--expected '{"status":"running"}'
# Listar e2e tests
fn ops e2e list
# Ver resultado
fn ops e2e show <id>