ShellCheck va deixar el toolkit net d'errors de forma, però no sap res del que fa. No et pot dir si veloz_percentatge 0 0 retorna alguna cosa sensata en lloc de petar, si copia.sh respecta de debò els trenta dies de retenció, ni si l'awk amb què vam accelerar l'informe a 08-02 produeix les mateixes xifres que el bucle que va substituir. Això només ho respon executar el codi i comparar el resultat amb l'esperat. Aquesta lliçó respon a la pregunta amb què obria el mòdul —com sé que un canvi no trenca res?— amb Bats, el marc de proves de Bash.

Contingut

  1. Per què els scripts d'operacions es proven poc, i per què és un error
  2. Què val la pena provar i què no
  3. Provar a mà primer
  4. Instal·lar bats-core i les seves llibreries
  5. Anatomia d'un fitxer .bats
  6. Executar les proves
  7. Aïllar la prova de l'entorn
  8. Dobles de prova: substituir una ordre externa
  9. Estructura de tests/ al toolkit
  10. Hook, CI i criteri de cobertura
  11. Aplicació: tests/comu.bats

  1. Per què els scripts d'operacions es proven poc, i per què és un error

Ningú no discuteix que una aplicació web es prova. Amb els scripts de sistema el costum és un altre: s'executen a mà un cop, «funciona», i a producció. Les excuses habituals tenen la seva lògica aparent: «només és un script de 80 línies» —que executa rm -rf com a root cada nit—, «el provo executant-lo» —un cop, amb les dades d'avui, a la teva màquina, amb el teu PATH— i «no hi ha manera de provar una cosa que toca el sistema», que és fals i es resol a l'apartat 8.

L'argument a favor és més fort que tots aquests: els scripts d'operacions fallen de matinada i sense ningú al davant. Una aplicació web caiguda la veu un usuari en deu segons; copia.sh pot portar tres setmanes desant arxius buits i ningú no se n'assabenta fins que cal restaurar. I hi ha un segon argument més pràctic: les proves fan possible refactoritzar. Tot el mòdul 8 consisteix a canviar codi que funciona; sense proves, cada canvi és un acte de fe, i amb elles la refactorització de rendiment de 08-02 es valida en dos segons.

  1. Què val la pena provar i què no

Què Rendibilitat Per què
Funcions pures de lib/comu.sh (veloz_percentatge, validadors) Molt alta Entrada → sortida, sense efectes; es proven en mil·lisegons
Funcions amb efectes acotats (veloz_log, escriptura de fitxers) Alta Es comprova el fitxer resultant en un temporal
Script complet: codi de sortida, sortida, fitxers que genera Mitjana-alta És el que de debò fa servir l'usuari, i on hi ha més bugs silenciosos
systemctl, ssh a la flota, enviament de correu real Baixa Es proven amb dobles, no de debò
Capçaleres, colors del terminal Nul·la Es veu a simple vista i canvia constantment

La regla: prova la lògica, no la lampisteria. Si una funció decideix alguna cosa —un llindar, un format, una validació, una suma—, prova-la. Si només encadena dues ordres del sistema, comprova el conjunt i no les peces.

  1. Provar a mà primer

Abans d'instal·lar res convé veure què automatitza Bats. Una funció es prova en una sessió interactiva:

$ source ~/veloz-ops/lib/comu.sh
$ veloz_percentatge 412 1284
32.1
$ veloz_percentatge 0 0
veloz_percentatge: el total no pot ser zero      # i $? val 1

# El mateix dialeg, escrit com a comprovacio automatica
[[ $(veloz_percentatge 412 1284) == "32.1" ]] || echo "FALLADA: percentatge basic"
veloz_percentatge 0 0 2>/dev/null          && echo "FALLADA: hauria de fallar amb total 0"

Això ja és una prova: executar amb una entrada coneguda i comparar amb la sortida esperada. Per a tres comprovacions pot ser suficient. El que Bats aporta quan en passes de tres és tot el que aquí falta: noms llegibles per cas, que una fallada no aturi les altres, capturar alhora sortida i codi de sortida, preparar i netejar l'entorn de cada prova i un resum final amb el compte d'encerts i fallades.

  1. Instal·lar bats-core i les seves llibreries

bats-core és el projecte mantingut avui (l'original sstephenson/bats està abandonat).

$ sudo apt install bats                    # rapid, versio una mica antiga
# O com a submoduls del repositori, que es el recomanable en equip (08-04)
$ git submodule add https://github.com/bats-core/bats-core.git tests/bats
$ git submodule add https://github.com/bats-core/bats-support.git tests/lib/bats-support
$ git submodule add https://github.com/bats-core/bats-assert.git tests/lib/bats-assert

Les dues llibreries auxiliars no són imprescindibles, però milloren molt els missatges: bats-support aporta el format d'errors i bats-assert les comprovacions assert_success, assert_failure, assert_output, assert_line i refute_output, que en fallar mostren què esperaven i què han obtingut en comptes d'un escarit «no ha passat».

  1. Anatomia d'un fitxer .bats

Un .bats és un script de Bash amb una sintaxi afegida: el bloc @test.

#!/usr/bin/env bats
# tests/comu.bats - proves de la llibreria del toolkit

setup() {
    load 'lib/bats-support/load'
    load 'lib/bats-assert/load'
    source "${BATS_TEST_DIRNAME}/../lib/comu.sh"
}

@test "veloz_percentatge calcula amb un decimal" {
    run veloz_percentatge 412 1284
    assert_success
    assert_output "32.1"
}

Les peces, una a una:

  • @test "descripció" { ... }: cada prova amb un nom que es llegeix a la sortida i que ha de dir quin comportament s'espera, no «prova 1».
  • run ordre ...: executa l'ordre capturant-ne la sortida i el codi de sortida sense que la fallada avorti la prova. És la peça central de Bats.
  • $status: codi de sortida del que ha executat run. $output: sortida combinada (stdout + stderr). ${lines[@]}: la mateixa sortida com a array, una línia per element.
  • setup() s'executa abans de cada prova i teardown() després, fins i tot si falla; setup_file()/teardown_file() un sol cop per fitxer, per a preparatius cars. load fitxer carrega un .bash relatiu al directori de la prova, skip "motiu" omet una prova i BATS_TEST_DIRNAME és el directori del .bats, per construir camins independents d'on es llanci.

Sense bats-assert les mateixes comprovacions s'escriuen amb [ "$status" -eq 0 ] i [ "$output" = "32.1" ], cosa que també és vàlida.

  1. Executar les proves

$ bats tests/
comu.bats
 ✓ veloz_percentatge calcula amb un decimal
 ✓ veloz_percentatge falla si el total es zero
 ✗ veloz_requereix detecta una ordre absent
   (in test file tests/comu.bats, line 41)
   -- command succeeded, but it was expected to fail --
3 tests, 1 failure

Opcions útils: --filter 'percentatge' executa només les proves el nom de les quals casi amb el patró, -t produeix sortida TAP pura —el format estàndard que consumeixen els sistemes de CI—, -x traça cada ordre per depurar i -j 4 executa en paral·lel. El codi de sortida és 0 si tot passa i 1 si alguna cosa falla, que és justament el que necessiten el hook i CI.

  1. Aïllar la prova de l'entorn

L'error més habitual en començar és escriure proves que depenen de la màquina: del /srv/veloz/dades real, del fus horari, que existeixi jq. Aquestes proves passen al teu portàtil, fallen a CI i acaben desactivades. La solució és que cada prova construeixi el seu propi món en un directori temporal:

setup() {
    load 'lib/bats-support/load'
    load 'lib/bats-assert/load'
    export TZ=UTC LC_ALL=C                       # res de formats locals
    DIR_PROVA="$(mktemp -d "${BATS_TMPDIR}/veloz.XXXXXX")"
    export VELOZ_DIR_DADES="$DIR_PROVA/dades" VELOZ_DIR_LOGS="$DIR_PROVA/logs"
    mkdir -p "$VELOZ_DIR_DADES" "$VELOZ_DIR_LOGS"
    cat > "$VELOZ_DIR_DADES/enviaments.csv" <<'EOF'
id_enviament,data,ciutat,repartidor,estat,import
E000001,2026-08-03,Valencia,alopez,lliurat,24.50
E000002,2026-08-03,Sevilla,mgarcia,incidencia,31.00
E000003,2026-08-03,Valencia,jruiz,en_repartiment,18.75
EOF
    source "${BATS_TEST_DIRNAME}/../lib/comu.sh"
}

teardown() { rm -rf "$DIR_PROVA"; }

Tres enviaments són suficients: hi ha una ciutat repetida, dos estats i tres repartidors, que és tot el que necessita qualsevol agregació. BATS_TMPDIR és el temporal que gestiona Bats; en versions recents existeix a més BATS_TEST_TMPDIR, creat i esborrat automàticament per prova.

  1. Dobles de prova: substituir una ordre externa

Aquesta és la tècnica que fa provables els scripts de sistema, i és més simple del que sembla. Bash busca les ordres recorrent $PATH d'esquerra a dreta; si hi poses al principi un directori amb un script anomenat curl, aquest és el curl que s'executa.

setup() {
    DIR_PROVA="$(mktemp -d "${BATS_TMPDIR}/veloz.XXXXXX")"
    mkdir -p "$DIR_PROVA/bin"
    cat > "$DIR_PROVA/bin/curl" <<'EOF'
#!/usr/bin/env bash
printf '%s\n' "$*" >> "$REGISTRE_CRIDES"        # anota com se l'ha cridat
printf '{"estat":"ok","enviaments":128}\n'
EOF
    chmod +x "$DIR_PROVA/bin/curl"
    export REGISTRE_CRIDES="$DIR_PROVA/crides.txt"
    export PATH="$DIR_PROVA/bin:$PATH"           # el doble te prioritat
    source "${BATS_TEST_DIRNAME}/../lib/comu.sh"
}

@test "veloz_api_get retorna el JSON de l'API" {
    run veloz_api_get /metricas
    assert_success
    assert_output --partial '"enviaments":128'
}

@test "veloz_api_get fa servir --netrc i no credencials a la linia d'ordres" {
    veloz_api_get /salud >/dev/null
    run grep -c -- '--netrc' "$REGISTRE_CRIDES"
    assert_output "1"
}

El doble aconsegueix tres coses, i només la primera és «no fer servir la xarxa». Determinisme: la resposta és sempre la mateixa, així que la prova no falla perquè l'API vagi lenta. Casos impossibles de provocar: canviant el doble perquè retorni exit 7 o un JSON truncat es prova la gestió d'errors, que és justament el codi que mai no s'exercita a mà. I verificació de la crida: el registre permet comprovar com s'ha invocat, que és com s'ha provat més amunt l'ús de --netrc de 08-03.

La mateixa tècnica cobreix la resta: un doble de systemctl que retorni inactive prova la branca d'alerta d'estat-servei.sh; un d'ssh prova flota.sh sense sortir de la màquina; un de mail verifica que vigilant.sh avisa sense enviar res a ningú. I per a copia.sh no cal doble: n'hi ha prou d'apuntar VELOZ_DIR_DADES i VELOZ_DIR_COPIA al temporal perquè treballi sobre quatre fitxers ficticis en lloc de sobre el disc real.

  1. Estructura de tests/ al toolkit

El directori tests/ conté els submòduls (bats/, lib/) i un .bats per unitat provada: comu.bats per a les funcions de la llibreria, informe.bats per a informe-diari.sh d'extrem a extrem i copia.bats per a la retenció i la restauració.

Fitxer Casos normals Casos límit Casos d'error
comu.bats veloz_percentatge 412 128432.1 numerador 0, total 0, valor més gran que el total arguments no numèrics → codi 1
informe.bats CSV de 4 files → totals per ciutat CSV només amb capçalera, camp buit CSV inexistent → codi 2 i missatge
copia.bats crea el .tar.gz i el seu .sha256 0 fitxers; nom amb espais destí sense permisos → codi ≠ 0

Els casos límit són els que més bugs troben: un CSV només amb la capçalera fa que molts informes divideixin entre zero, un import 1.234,50 trenca la suma i un fitxer amb espais desmunta el find mal escrit. I provar el codi de sortida no és un detall: és l'única cosa que veu el timer de systemd de 07-05 i el que decideix si s'envia una alerta.

  1. Hook, CI i criteri de cobertura

El hook pre-commit de 08-04 ja invocava bats tests/. A CI s'afegeix al costat de l'anàlisi estàtica, amb bats --formatter tap tests/. Un detall important perquè això sigui sostenible: les proves han de ser ràpides. Si triguen quaranta segons, el hook es torna molest i algú començarà a fer servir --no-verify; amb dobles i dades fictícies, la suite completa del toolkit triga menys de dos segons, i això és el que garanteix que s'executi sempre.

No cal provar-ho tot. El criteri, per ordre de prioritat: el que ja s'ha trencat alguna vegada —cada incident resolt es converteix en una prova, i aquesta és la regla més valuosa, perquè garanteix que la mateixa fallada no torna i fa créixer la suite justament per on el sistema és fràgil—; el que decideix alguna cosa (llindars, validacions, càlculs); el que és destructiu (retenció, esborrat, sobreescriptura), perquè una fallada aquí no es desfà; i els codis de sortida dels scripts que governen alertes i timers. Perseguir el 100 % de cobertura en Bash produeix proves fràgils que es trenquen amb cada canvi de format i acaben desactivades, cosa pitjor que no tenir-les.

  1. Aplicació: tests/comu.bats

#!/usr/bin/env bats
# tests/comu.bats - proves de lib/comu.sh del toolkit de Veloz Envios
# setup() i teardown() com a l'apartat 7, amb VELOZ_DIR_LOGS al temporal

@test "percentatge: cas normal amb un decimal" {
    run veloz_percentatge 412 1284
    assert_success
    assert_output "32.1"
}

@test "percentatge: numerador zero retorna 0.0" {
    run veloz_percentatge 0 1284
    assert_success
    assert_output "0.0"
}

@test "percentatge: total zero falla i no divideix" {
    run veloz_percentatge 5 0
    assert_failure
    assert_output --partial "total"
}

@test "requereix: falla i anomena l'ordre absent" {
    run veloz_requereix ordre_que_no_existeix_12345
    assert_failure
    assert_output --partial "ordre_que_no_existeix_12345"
}

@test "log: escriu nivell, missatge i marca de temps ISO-8601" {
    veloz_log INFO "copia completada"
    run cat "$VELOZ_DIR_LOGS/veloz-ops.log"
    assert_output --partial "[INFO] copia completada"
    assert_line --regexp '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}'
}

@test "log: no interpreta el contingut del missatge" {
    veloz_log INFO 'perill $(id) i *'
    run cat "$VELOZ_DIR_LOGS/veloz-ops.log"
    assert_output --partial 'perill $(id) i *'
}

Cinc proves, menys de dos segons, i cobreixen els tres tipus de cas (a les quals convé afegir el cas positiu veloz_requereix bash). L'última mereix atenció: comprova que veloz_log tracta el seu argument com a text i no l'expandeix, que és exactament la vulnerabilitat d'injecció de 08-03. Una prova pot vigilar una propietat de seguretat igual que vigila un càlcul.

Errors Habituals i Consells

  • Oblidar run. Sense ell, una ordre que falla avorta la prova i no pots comprovar $status.
  • Proves que depenen de la màquina. Camins reals, fus horari, jq instal·lat. Aïlla-ho tot a setup amb temporal, TZ i LC_ALL.
  • No netejar al teardown. Les proves es contaminen entre si i fallen segons l'ordre d'execució.
  • Provar només el camí feliç. Els bugs viuen al CSV buit, al camp absent i al disc ple.
  • Consell: converteix cada incident en una prova. És la manera més barata que la suite cobreixi justament el que importa.
  • Consell: bats -x --filter 'nom' executa una sola prova amb traça completa; és el set -x de 05-03 aplicat a les proves.

Exercicis

Exercici 1. Escriu una prova Bats que verifiqui que informe-diari.sh falla amb codi 2 i un missatge a stderr quan el CSV d'entrada no existeix.

Exercici 2. Explica per què aquesta prova està malament i reescriu-la.

@test "la copia funciona" {
    run /home/veloz/veloz-ops/bin/copia.sh
    [ "$status" -eq 0 ]
}

Solucions

Solució 1.

@test "informe: CSV inexistent falla amb codi 2 i missatge" {
    run "${BATS_TEST_DIRNAME}/../bin/informe-diari.sh" -f "$DIR_PROVA/no-existeix.csv"
    assert_failure 2
    assert_output --partial "no-existeix.csv"
}

assert_failure 2 exigeix el codi exacte, no una fallada qualsevol: és el que distingeix «error d'ús» d'«error intern» segons els codis convencionals de 05-03. Com que run captura stdout i stderr junts, assert_output veu el missatge encara que s'escrigui a stderr.

Solució 2. Té quatre problemes: fa servir un camí absolut de l'usuari, així que només funciona en aquella màquina; executa la còpia sobre les dades reals, amb risc de sobreescriure la còpia de producció des d'una prova; només comprova el codi de sortida, no que la còpia contingui res; i el seu nom no diu quin comportament s'espera.

@test "copia: crea el tar.gz i la seva suma de comprovacio" {
    export VELOZ_DIR_DADES="$DIR_PROVA/dades" VELOZ_DIR_COPIA="$DIR_PROVA/copia"
    mkdir -p "$VELOZ_DIR_DADES" "$VELOZ_DIR_COPIA"
    printf 'contingut\n' > "$VELOZ_DIR_DADES/enviaments.csv"
    run "${BATS_TEST_DIRNAME}/../bin/copia.sh"
    assert_success
    run bash -c 'ls "$VELOZ_DIR_COPIA"/*.tar.gz "$VELOZ_DIR_COPIA"/*.sha256'
    assert_success
    run tar -tzf "$(ls "$VELOZ_DIR_COPIA"/*.tar.gz)"
    assert_output --partial "enviaments.csv"
}

Ara la prova és independent de la màquina, treballa sobre dades fictícies i verifica l'efecte: que l'arxiu existeix, que existeix la seva suma de comprovació (07-03) i que a dins hi ha el fitxer esperat.

Conclusió

Provar scripts d'operacions no és un luxe: és l'única detecció primerenca disponible en un codi que s'executa de matinada i sense ningú al davant, on una còpia buida pot passar setmanes inadvertida. El més rendible són les funcions pures de lib/comu.sh, seguides del comportament d'extrem a extrem de cada script —codi de sortida, sortida i fitxers que crea—; la lampisteria sense lògica no es prova. La idea és la mateixa que la comprovació casolana [[ $(f x) == esperat ]] || echo FALLADA, i Bats hi afegeix el que a aquella li falta: noms llegibles, aïllament entre casos, resum final i, sobretot, run, que captura $status, $output i ${lines[@]} sense que una fallada avorti la prova. Amb setup/teardown cada cas construeix el seu món en un directori temporal amb dades fictícies de Veloz Envíos i fixa TZ i LC_ALL, de manera que la prova no depengui de la màquina —el motiu principal pel qual les suites acaben desactivades—. Els dobles de prova són la tècnica que ho fa tot possible: un script fals anomenat curl, systemctl, ssh o mail en un directori al principi del PATH permet provar veloz_api_get sense xarxa, flota.sh sense servidors i copia.sh sense tocar el disc, a més de provocar els errors que mai no veuries a mà i de verificar com s'ha invocat l'ordre. La suite s'organitza en tests/comu.bats, tests/informe.bats i tests/copia.bats, cobrint en cadascuna casos normals, límit i d'error amb els seus codis de sortida, i s'executa al hook pre-commit i a CI (08-04, 08-05), cosa que exigeix que sigui ràpida: amb dobles, menys de dos segons. I el criteri de cobertura no és un percentatge, sinó una prioritat: el que ja s'ha trencat un cop, el que decideix alguna cosa i el que és destructiu.

Ja tenim un toolkit llegible, ràpid, auditat, versionat, analitzat i provat. Falta una decisió que hem donat per suposada tot el curs: que l'intèrpret és Bash 5 sobre Ubuntu. La lliçó 08-07 la posa a prova: què passa quan un script s'ha d'executar allà on /bin/sh és dash, ash d'Alpine o ksh, quines construccions de les que fem servir són bashismes i quin és el seu equivalent POSIX, per què les diferències entre les eines GNU i BSD fan més mal que les del mateix shell, com comprovar la portabilitat amb checkbashisms, shellcheck -s sh i dash -n, i quan POSIX pur val la pena i quan és un llast. Amb aquesta lliçó es tanca el mòdul sencer.

Curs de Programació en Bash

Mòdul 1: Introducció a Bash

Mòdul 2: Ordres Bàsiques de Bash

Mòdul 3: Fonaments de Scripting

Mòdul 4: Scripting Intermedi

Mòdul 5: Tècniques Avançades de Scripting

Mòdul 6: Treballar amb Eines Externes

Mòdul 7: Automatització i Programació

Mòdul 8: Bones Pràctiques i Optimització

Mòdul 9: Projectes del Món Real

© Copyright 2026. Tots els drets reservats