El sistema de tipos de Gleam ya elimina en compilación una buena parte de los bugs que en otros lenguajes solo se detectan con tests. Pero los tipos no lo comprueban todo: la lógica de negocio, los casos límite, el comportamiento de funciones con efectos secundarios, todo eso necesita tests. Gleam tiene gleeunit, su framework de testing oficial, y el comando gleam test para ejecutarlos.
Configurar gleeunit
gleeunit se añade como dependencia de desarrollo. Si creas un proyecto con gleam new, ya viene incluido en gleam.toml. Si no, puedes añadirlo manualmente:
gleam add --dev gleeunit
El fichero gleam.toml:
[dev-dependencies] gleeunit = ">= 1.0.0"
Los ficheros de test van en el directorio test/ del proyecto y tienen el sufijo _test.gleam:
mi_proyecto/ ??? src/ ? ??? mi_proyecto.gleam ??? test/ ? ??? mi_proyecto_test.gleam ??? gleam.toml
Escribir el primer test
// test/mi_proyecto_test.gleam
import gleeunit
import gleeunit/should
import mi_proyecto
pub fn main() {
gleeunit.main()
}
pub fn suma_test() {
mi_proyecto.suma(2, 3)
|> should.equal(5)
}
pub fn suma_negativa_test() {
mi_proyecto.suma(-1, -2)
|> should.equal(-3)
}
Las funciones de test tienen el sufijo _test. gleeunit las detecta automáticamente y las ejecuta. El resultado va al final de la función usando el pipe operator con should.equal. Ese estilo de «valor |> should.equal(esperado)» es muy natural en Gleam.
Las aserciones de gleeunit/should
import gleeunit/should // Igualdad value |> should.equal(expected) value |> should.not_equal(other) // Booleanos value |> should.be_true value |> should.be_false // Option option_value |> should.be_some option_value |> should.be_none option_value |> should.be_some |> should.equal(42) // Result result_value |> should.be_ok result_value |> should.be_error result_value |> should.be_ok |> should.equal(42)
Testar código con Result y Option
Como Gleam no tiene excepciones, los tests con Result y Option son muy directos. No hay que simular excepciones ni usar fixtures especiales:
import gleeunit/should
import gleam/int
pub fn parse_numero_valido_test() {
int.parse("42")
|> should.be_ok
|> should.equal(42)
}
pub fn parse_numero_invalido_test() {
int.parse("abc")
|> should.be_error
}
pub fn divide_por_cero_test() {
mi_modulo.divide(10, 0)
|> should.be_error
|> should.equal("División por cero")
}
Testar actores y código concurrente
Para testar actores del módulo gleam_otp, el patrón habitual es arrancar el actor, enviarle mensajes y comprobar el estado o las respuestas con un Subject tipado:
import gleeunit/should
import gleam/otp/actor
import gleam/erlang/process
import mi_proyecto/contador
pub fn contador_incrementa_test() {
let assert Ok(actor) = contador.start(0)
process.send(actor, contador.Increment)
process.send(actor, contador.Increment)
let subject = process.new_subject()
process.send(actor, contador.GetCount(subject))
let assert Ok(count) = process.receive(subject, 1000)
count |> should.equal(2)
actor.stop(actor)
}
Ejecutar los tests
# Ejecutar todos los tests gleam test # Ejecutar tests para un target específico gleam test --target javascript gleam test --target erlang
La salida indica qué tests han pasado, cuáles han fallado y el motivo del fallo con el valor real y el esperado. No hace falta configuración adicional: gleam test descubre y ejecuta todo lo que haya en el directorio test/.
Por qué los tests son más simples con tipos
El sistema de tipos de Gleam tiene un efecto directo en la escritura de tests. Como no hay null, no necesitas tests para «¿qué pasa si el valor es null?». Como no hay excepciones, no necesitas tests para «¿qué pasa si lanza una excepción inesperada?». Los tests se concentran en la lógica real: ¿devuelve el valor correcto para esta entrada? ¿maneja bien los casos de error representados como Result?
Eso no significa que necesites menos tests, pero sí que los tests que escribes son más directos. La cobertura de tipos la da el compilador; la cobertura de lógica la dan los tests.
Integración con CI
Como gleam test es un comando único sin configuración extra, integrarlo en cualquier sistema de CI es trivial. Un pipeline básico en GitHub Actions para un proyecto Gleam:
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: "26"
gleam-version: "1.7.0"
- run: gleam test
Con eso tienes tests automáticos en cada push. El compilador de Gleam también puede fallar el CI si hay errores de tipos, lo que da una doble red de seguridad: primero el compilador, luego los tests.
Con este artículo cerramos la serie sobre Gleam. Hemos cubierto desde la introducción al lenguaje hasta el testing, pasando por tipos, concurrencia, interop con Elixir y Erlang, el target JavaScript y el ecosistema de paquetes. Gleam 1.x es un lenguaje maduro y con ideas claras; si el tipado estático en la BEAM te llama la atención, merece la pena dedicarle tiempo.
Imagen: Pexels / Daniil Komov
