Pruebas unitarias de funciones con Bats-core
Estructure archivos de prueba, aserciones y configuración y desmontaje para verificar funciones individuales de Bash.
Pruebas unitarias de funciones con Bats-core es una lección gratuita de Linux Command Line & Bash Scripting Mastery en CoddyKit. Esta es la lección 1 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Linux Command Line & Bash Scripting Mastery, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Linux Command Line & Bash Scripting Mastery incluye 4 lecciones en total.
¿Qué es Bats-core y por qué usarlo?
Bats-core (Bash Automated Testing System) es el framework de facto para realizar pruebas unitarias en Bash. Permite escribir pruebas estructuradas y repetibles para sus funciones y scripts de shell, del mismo modo que usaría JUnit para Java o pytest para Python.
- Cada prueba es un bloque
@testcon una descripción comprensible. - Las pruebas tienen éxito cuando cada comando que contienen devuelve el código de salida
0. - Las pruebas fallan ante el primer código de salida distinto de cero o cuando falla una aserción.
- La salida es compatible con TAP, por lo que los sistemas de CI (GitHub Actions, Jenkins, GitLab CI) la interpretan de forma nativa.
Instálelo mediante su gestor de paquetes o clone el repositorio:
# Install via git (recommended — always latest)
git clone https://github.com/bats-core/bats-core.git
cd bats-core && sudo ./install.sh /usr/local
# Or on macOS with Homebrew
brew install bats-core
# Verify installation
bats --version
# bats 1.x.ySu primer archivo de pruebas de Bats
Un archivo de pruebas de Bats tiene la extensión .bats y comienza con un shebang especial. El bloque de construcción principal es la directiva @test, seguida de una cadena de descripción y un bloque de comandos.
- El shebang
#!/usr/bin/env batsindica al shell cómo ejecutar el archivo. - Cada bloque
@testes un caso de prueba independiente. - Puede ejecutar un solo archivo con
bats my_tests.batso un directorio completo conbats test/.
A continuación se muestra la estructura mínima de un archivo de pruebas de Bats:
#!/usr/bin/env bats
# File: test/hello.bats
@test "echo outputs the expected string" {
result=$(echo "hello world")
[ "$result" = "hello world" ]
}
@test "false command causes test to fail" {
# Uncommenting the next line would make this test fail:
# false
true
}Cargar la función que se va a probar con 'load'
En proyectos reales, las funciones de Bash se encuentran en archivos de biblioteca, no dentro del propio archivo de pruebas. Bats proporciona el helper load para cargar archivos externos relativos al directorio del archivo de pruebas.
load '../lib/math.sh'carga el archivo antes de ejecutar cada prueba.- Después de cargarlo, todas las funciones definidas en ese archivo están disponibles en sus bloques de prueba.
- Mantenga las funciones de biblioteca en un directorio
lib/y las pruebas en un directoriotest/para mantener una separación clara.
Ejemplo de estructura del proyecto y de la prueba correspondiente:
# Project layout:
# lib/math.sh <- functions to test
# test/math.bats <- test file
# lib/math.sh
add() {
echo $(( $1 + $2 ))
}
divide() {
if [ "$2" -eq 0 ]; then
echo "Error: division by zero" >&2
return 1
fi
echo $(( $1 / $2 ))
}
# test/math.bats
#!/usr/bin/env bats
load '../lib/math.sh'
@test "add returns correct sum" {
result=$(add 3 4)
[ "$result" = "7" ]
}Aserciones principales: run, $status, $output
El comando run es el núcleo de las pruebas con Bats. En lugar de ejecutar un comando directamente, envolverlo con run captura su código de salida y su salida sin hacer que la prueba falle de inmediato.
$status— contiene el código de salida del último comandorun.$output— contiene la salida stdout combinada del último comandorun.$lines— es un array en el que cada elemento corresponde a una línea de salida (${lines[0]},${lines[1]}, etc.).
Esto permite comprobar tanto los casos de éxito como los de error:
#!/usr/bin/env bats
load '../lib/math.sh'
@test "divide 10 by 2 returns 5" {
run divide 10 2
[ "$status" -eq 0 ]
[ "$output" = "5" ]
}
@test "divide by zero returns exit code 1" {
run divide 10 0
[ "$status" -eq 1 ]
}
@test "divide by zero prints error message" {
run divide 10 0
# $output captures stderr too when redirected inside the function
[[ "$output" == *"division by zero"* ]]
}Usar bats-assert para crear aserciones expresivas
Las aserciones integradas [ ] funcionan, pero proporcionan mensajes de error poco útiles. La biblioteca auxiliar bats-assert ofrece funciones de aserción expresivas que muestran exactamente qué salió mal.
assert_success— comprueba que$statussea 0.assert_failure— comprueba que$statussea distinto de cero.assert_output— comprueba que$outputsea igual a la cadena indicada.assert_output --partial— comprueba que la salida contenga la subcadena.refute_output --partial— comprueba que la salida NO contenga la subcadena.
Instálela clonando bats-core/bats-assert en una carpeta test/helpers/ y, después, cárguela:
#!/usr/bin/env bats
# Load bats-assert (cloned into test/helpers/bats-assert)
load 'helpers/bats-assert/load'
load '../lib/math.sh'
@test "add 5 and 3 gives 8" {
run add 5 3
assert_success
assert_output "8"
}
@test "divide by zero fails with descriptive message" {
run divide 9 0
assert_failure
assert_output --partial "division by zero"
}
@test "add does not output an error" {
run add 1 1
refute_output --partial "Error"
}setup y teardown: hooks del ciclo de vida de las pruebas
Bats proporciona dos funciones especiales — setup y teardown — que se ejecutan automáticamente alrededor de cada prueba. Úselas para preparar y limpiar el estado compartido, de modo que cada prueba comience en un entorno conocido.
setup()se ejecuta antes de cada bloque@testindividual.teardown()se ejecuta después de cada bloque@testindividual, incluso si la prueba falla.- Usos habituales: crear directorios temporales, establecer variables de entorno y eliminar archivos temporales después de la prueba.
#!/usr/bin/env bats
load '../lib/fileutils.sh'
setup() {
# Create a fresh temp directory before every test
TEST_DIR=$(mktemp -d)
export TEST_DIR
}
teardown() {
# Always clean up, even on test failure
rm -rf "$TEST_DIR"
}
@test "write_file creates a file with correct content" {
run write_file "$TEST_DIR/hello.txt" "hello world"
assert_success
[ -f "$TEST_DIR/hello.txt" ]
[ "$(cat "$TEST_DIR/hello.txt")" = "hello world" ]
}
@test "write_file fails when directory does not exist" {
run write_file "/nonexistent/dir/file.txt" "data"
assert_failure
}setup_file y teardown_file: hooks a nivel de suite
A veces solo necesita configurar recursos costosos una vez por archivo, no antes de cada prueba. Bats proporciona setup_file y teardown_file para este propósito.
setup_file()se ejecuta una vez antes de todas las pruebas del archivo.teardown_file()se ejecuta una vez después de todas las pruebas del archivo.- Use
BATS_FILE_TMPDIR(disponible automáticamente) para compartir datos entresetup_filey sus pruebas; las variables normales no persisten entre subshells.
Un caso de uso habitual es iniciar un servidor simulado o compilar un binario una sola vez y desmontarlo al final:
#!/usr/bin/env bats
setup_file() {
# Build the project binary once for all tests in this file
make build --silent
export BINARY="$PWD/bin/myapp"
echo "Binary built: $BINARY"
}
teardown_file() {
# Remove the binary after all tests complete
rm -f "$BINARY"
echo "Cleaned up binary"
}
setup() {
# Still runs before each individual test
TEST_TMP=$(mktemp -d)
}
teardown() {
rm -rf "$TEST_TMP"
}
@test "myapp --version outputs version string" {
run "$BINARY" --version
assert_output --partial "1.0"
}Probar funciones que modifican archivos
Un patrón muy habitual consiste en probar funciones de Bash que leen del sistema de archivos o escriben en él. La técnica clave es usar directorios temporales (mediante mktemp -d en setup) para que las pruebas nunca toquen archivos reales ni interfieran entre sí.
- Trabaje siempre dentro de
$TEST_DIR(o$BATS_TEST_TMPDIR, disponible automáticamente en las versiones recientes de Bats). - Use la biblioteca auxiliar
bats-filepara realizar aserciones claras sobre archivos, comoassert_file_existsyassert_file_contains. - No codifique nunca rutas como
/tmp/myfile; las ejecuciones de pruebas en paralelo entrarían en conflicto.
#!/usr/bin/env bats
load 'helpers/bats-assert/load'
load 'helpers/bats-file/load'
load '../lib/fileutils.sh'
setup() {
TEST_DIR="$BATS_TEST_TMPDIR"
}
# lib/fileutils.sh defines:
# append_line() { echo "$2" >> "$1"; }
@test "append_line adds a line to an existing file" {
echo "first line" > "$TEST_DIR/log.txt"
run append_line "$TEST_DIR/log.txt" "second line"
assert_success
assert_file_contains "$TEST_DIR/log.txt" "second line"
}
@test "append_line creates file if it does not exist" {
run append_line "$TEST_DIR/new.txt" "hello"
assert_success
assert_file_exists "$TEST_DIR/new.txt"
}Simular comandos externos
Las funciones suelen llamar a programas externos como curl, aws o git. En las pruebas unitarias quiere probar su lógica, no el comando externo real. La técnica de simulación más sencilla en Bats consiste en definir una función de shell con el mismo nombre que el comando dentro de setup; esta tiene prioridad sobre el binario real.
- Defina una función como
curl() { echo 'mocked response'; return 0; }ensetupy expórtela. - Use
export -f curlpara que la función esté disponible en los subshells que iniciarun. - También puede escribir el mock en un archivo temporal incluido en
PATHpara escenarios más complejos.
#!/usr/bin/env bats
load 'helpers/bats-assert/load'
load '../lib/network.sh'
# lib/network.sh defines:
# fetch_status() {
# local url="$1"
# local code
# code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
# echo "$code"
# }
setup() {
# Override 'curl' with a mock function
curl() {
# Simulate a 200 OK response
echo "200"
return 0
}
export -f curl
}
@test "fetch_status returns 200 when curl reports 200" {
run fetch_status "https://example.com"
assert_success
assert_output "200"
}Omitir y etiquetar pruebas
No todas las pruebas pueden ejecutarse siempre; a veces necesita una conexión de red real, una herramienta específica o un sistema operativo determinado. Bats proporciona skip para omitir condicionalmente una prueba con un mensaje informativo, en lugar de comentarla o romper la suite.
- Llame a
skip "reason"en cualquier lugar dentro de un bloque@testpara omitir esa prueba. - Las pruebas omitidas aparecen en la salida como
Sy no cuentan como fallos. - Bats 1.5 y versiones posteriores admiten tags: anote las pruebas con
# bats test_tags=slow,networky filtre conbats --filter-tags network test/.
#!/usr/bin/env bats
# bats test_tags=network
@test "API returns valid JSON" {
# Skip if no internet connectivity
if ! ping -c1 -W1 8.8.8.8 &>/dev/null; then
skip "No network connection available"
fi
run curl -s "https://api.example.com/health"
assert_success
assert_output --partial '"status"'
}
# bats test_tags=unit
@test "slug function lowercases and replaces spaces" {
# Always runs — pure function, no external deps
slug() { echo "$1" | tr '[:upper:]' '[:lower:]' | tr ' ' '-'; }
run slug "Hello World"
assert_output "hello-world"
}
# Run only unit tests:
# bats --filter-tags unit test/Estructurar una suite de pruebas completa
Un proyecto de Bats bien organizado sigue una estructura de directorios predecible, lo que facilita la incorporación de nuevos colaboradores y la integración con los pipelines de CI.
Estructura recomendada:
lib/— funciones de Bash de producción (un archivo por responsabilidad:math.sh,fileutils.sh).test/— un archivo.batspor cada archivo de biblioteca (math.bats,fileutils.bats).test/helpers/— bats-assert, bats-file y bats-support como submódulos de git.Makefile— un objetivotestpara que los colaboradores solo tengan que ejecutarmake test.
Ejecute la suite completa con un solo comando:
# Makefile
.PHONY: test
test:
bats test/
# Run all tests recursively (Bats 1.5+)
# bats --recursive test/
# Run a specific file
# bats test/math.bats
# Run with verbose (TAP) output for CI
# bats --tap test/
# Example directory tree:
# .
# |-- lib/
# | |-- math.sh
# | `-- fileutils.sh
# |-- test/
# | |-- helpers/
# | | |-- bats-assert/
# | | `-- bats-file/
# | |-- math.bats
# | `-- fileutils.bats
# `-- MakefileComprobación de conocimientos: aserciones de Bats-core
Compruebe su comprensión del mecanismo principal de pruebas de Bats-core.
Recapitulación: pruebas unitarias de Bash con Bats-core
En esta lección ha aprendido a estructurar y escribir pruebas unitarias para funciones individuales de Bash mediante Bats-core. Estas son las ideas principales:
- Estructura del archivo de pruebas — use el shebang
#!/usr/bin/env batsy bloques@testcon nombres descriptivos. load— cargue sus archivos de biblioteca para que las funciones estén disponibles en las pruebas sin copiar y pegar código.run+$status+$output— el trío fundamental; use siemprerunpara capturar los resultados sin provocar un fallo inmediato de la prueba.- bats-assert — prefiera
assert_success,assert_failureyassert_outputa[ ]sin abstracciones, para obtener mensajes de error fáciles de interpretar. setup/teardown— se ejecutan antes y después de cada prueba;setup_file/teardown_filese ejecutan una vez por archivo.- Simulación — sustituya los comandos externos mediante funciones de shell con el mismo nombre, exportadas con
export -f. skip— omita condicionalmente las pruebas que dependen de recursos no disponibles.- Estructura del proyecto — mantenga
lib/,test/ytest/helpers/separados para facilitar el mantenimiento y la integración con CI.
Estos patrones proporcionan a sus proyectos de Bash la misma disciplina de pruebas que aplicaría a cualquier proyecto de software moderno.
Preguntas frecuentes
¿La lección «Pruebas unitarias de funciones con Bats-core» es gratis?
Sí — el texto completo de «Pruebas unitarias de funciones con Bats-core» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Linux Command Line & Bash Scripting Mastery, actualiza a CoddyKit PRO. El curso de Linux Command Line & Bash Scripting Mastery incluye 4 lecciones en total.
¿Qué aprenderé en «Pruebas unitarias de funciones con Bats-core»?
Estructure archivos de prueba, aserciones y configuración y desmontaje para verificar funciones individuales de Bash. Practicas Linux Command Line & Bash Scripting Mastery con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Linux Command Line & Bash Scripting Mastery?
No se requiere experiencia previa. Linux Command Line & Bash Scripting Mastery en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 1 de 4.
¿Cuánto tiempo toma la lección «Pruebas unitarias de funciones con Bats-core»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Linux Command Line & Bash Scripting Mastery?
Sí. Cada lección de Linux Command Line & Bash Scripting Mastery incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Pruebas unitarias de funciones con Bats-core
- Simulación de comandos y sustitución de herramientas externas
- Fixtures, entornos temporales y cobertura
- Ejecución de pruebas de shell en pipelines de CI