Pruebas Bash
Marcos de pruebas y patrones para scripts de shell, centrados en shunit2 y shellspec.
Selección de Marco
| Marco | Fortalezas | Mejor para |
|---|---|---|
| shunit2 | Estilo xUnit, simple, portátil | Pruebas unitarias, pruebas de funciones |
| shellspec | Estilo BDD, moderno, extenso | Especificaciones de comportamiento, proyectos completos |
shunit2
Instalación
# Descarga directa
curl -L https://github.com/kward/shunit2/raw/master/shunit2 -o shunit2
# O mediante gestor de paquetes
apt install shunit2
brew install shunit2
Estructura Básica de Prueba
#!/usr/bin/env bash
# Incluir el script que se está probando
source ./mi_script.sh
# Las funciones de prueba comienzan con test
test_adicion() {
resultado=$(sumar 2 3)
assertEquals "2 + 3 debería ser 5" "5" "$resultado"
}
test_salida_cadena() {
resultado=$(saludar "Mundo")
assertEquals "Hola, Mundo" "$resultado"
}
test_creacion_archivo() {
crear_archivo_temporal
assertTrue "El archivo temporal debería existir" "[ -f /tmp/archivo_prueba ]"
}
test_codigo_salida() {
validar_entrada "valido"
assertEquals "Debería devolver 0 para entrada válida" 0 $?
}
# Configuración y limpieza
oneTimeSetUp() {
# Ejecutar una vez antes de todas las pruebas
export DIR_PRUEBA=$(mktemp -d)
}
oneTimeTearDown() {
# Ejecutar una vez después de todas las pruebas
rm -rf "$DIR_PRUEBA"
}
setUp() {
# Ejecutar antes de cada prueba
cd "$DIR_PRUEBA"
}
tearDown() {
# Ejecutar después de cada prueba
rm -f "$DIR_PRUEBA"/*
}
# Cargar shunit2
source shunit2
Aserciones shunit2
# Igualdad
assertEquals [mensaje] esperado actual
assertNotEquals [mensaje] inesperado actual
# Nulo/Vacío
assertNull [mensaje] valor
assertNotNull [mensaje] valor
# Booleano/Estado
assertTrue [mensaje] condición
assertFalse [mensaje] condición
# Misma referencia (comparación de cadenas)
assertSame [mensaje] esperado actual
assertNotSame [mensaje] inesperado actual
# Contiene (en cadena)
assertContains [mensaje] contenedor contenido
# Estado de salida
assertEquals 0 $?
Pruebas de Funciones en Aislamiento
#!/usr/bin/env bash
# pruebas_mis_funciones.sh
source ./mis_funciones.sh
test_calcular_suma_vacio() {
resultado=$(calcular_suma)
assertEquals "La suma vacía debería ser 0" "0" "$resultado"
}
test_calcular_suma_unico() {
resultado=$(calcular_suma 5)
assertEquals "5" "$resultado"
}
test_calcular_suma_multiple() {
resultado=$(calcular_suma 1 2 3 4 5)
assertEquals "15" "$resultado"
}
test_validar_email_valido() {
validar_email "[email protected]"
assertTrue "El email válido debería pasar" $?
}
test_validar_email_invalido() {
validar_email "no-es-un-email"
assertFalse "El email inválido debería fallar" $?
}
source shunit2
shellspec
Instalación
# Mediante curl
curl -fsSL https://git.io/shellspec | sh
# Mediante Homebrew
brew install shellspec
# Mediante gestores de paquetes
apt install shellspec
Estructura de Directorios
proyecto/
├── lib/
│ └── funciones.sh
├── spec/
│ ├── spec_helper.sh
│ ├── funciones_spec.sh
│ └── support/
│ └── fixtures/
└── .shellspec
Configuración (.shellspec)
--require spec_helper
--format documentation
--color
Estructura Básica de Especificación
Comparadores shellspec
# Comparadores de salida
The output should eq "coincidencia exacta"
The output should include "parcial"
The output should start with "prefijo"
The output should end with "sufijo"
The output should match pattern "*comodín*"
The output should be blank
# Comparadores de estado
The status should be success # salida 0
The status should be failure # salida distinta de cero
The status should eq 1 # código específico
# Comparadores de variables
The variable VAR should eq "valor"
The variable VAR should be defined
The variable VAR should be undefined
# Comparadores de archivos
The file "ruta" should be exist
The file "ruta" should be file
The file "ruta" should be directory
The file "ruta" should be readable
# Comparadores de rutas
The path "archivo.txt" should be exist
Mocking en shellspec
Describe 'función de despliegue'
# Mock de comando externo
curl() {
echo "respuesta simulada"
return 0
}
It 'llama al endpoint de la API'
When call desplegar "servidor"
The output should include "respuesta simulada"
End
End
Describe 'operaciones de archivos'
# Mock con anulación de función
Mock rm
echo "rm llamado con: $*"
End
It 'intenta eliminar archivo'
When call limpiar_temporal
The output should include "rm llamado con"
End
End
Helper de Especificación
# spec/spec_helper.sh
# Cargar funciones comunes
spec_helper_precheck() {
minimum_version "0.28.0"
}
spec_helper_loaded() {
# Configurar entorno de prueba
export MODO_PRUEBA=true
}
spec_helper_configure() {
# Importar funciones del proyecto
import 'lib/funciones.sh'
}
Patrones de Prueba
Pruebas de Códigos de Salida
# shunit2
test_salida_exitosa() {
ejecutar_comando "entrada_valida"
assertEquals 0 $?
}
test_salida_error() {
ejecutar_comando "entrada_invalida"
assertEquals 1 $?
}
# shellspec
It 'sale con 0 en éxito'
When call ejecutar_comando "entrada_valida"
The status should eq 0
End
Pruebas de stdout y stderr
# shunit2
test_stdout() {
resultado=$(mi_funcion 2>/dev/null)
assertEquals "salida esperada" "$resultado"
}
test_stderr() {
error=$(mi_funcion 2>&1 >/dev/null)
assertContains "$error" "mensaje de error"
}
# shellspec
It 'envía salida a stdout'
When call mi_funcion
The stdout should eq "salida esperada"
End
It 'envía error a stderr'
When call mi_funcion
The stderr should include "mensaje de error"
End
Pruebas con Fixtures
# Configurar fixtures de prueba
setUp() {
DIR_PRUEBA=$(mktemp -d)
cat > "$DIR_PRUEBA/config.json" <<EOF
{
"clave": "valor"
}
EOF
}
tearDown() {
rm -rf "$DIR_PRUEBA"
}
test_analisis_config() {
resultado=$(analizar_config "$DIR_PRUEBA/config.json")
assertEquals "valor" "$resultado"
}
Pruebas de Funciones Interactivas
# Proporcionar entrada mediante heredoc
test_interactiva() {
resultado=$(mi_prompt <<EOF
sí
EOF
)
assertEquals "confirmado" "$resultado"
}
# O usar printf
test_con_entrada() {
resultado=$(printf 'sí\n' | mi_prompt)
assertEquals "confirmado" "$resultado"
}
Ejecución de Pruebas
# shunit2
./script_prueba.sh
# shellspec
shellspec # Ejecutar todas las especificaciones
shellspec spec/archivo_spec.sh # Ejecutar especificación específica
shellspec --format tap # Salida TAP
shellspec --jobs 4 # Ejecución paralela
Buenas Prácticas
- Una aserción por prueba cuando sea práctico
- Nombres descriptivos de prueba explicando lo que se prueba
- Aislar pruebas - sin dependencias entre pruebas
- Probar casos límite - entrada vacía, caracteres especiales, datos grandes
- Limpiar recursos en tearDown
- Mockear comandos externos - no pruebes curl, prueba tu lógica
- Probar códigos de salida no solo la salida