Linux - Scripts Bash - Lancer des tests unitaires avec bats/bats
Découvrez comment tester unitairement vos scripts Bash sous Linux avec le puissant framework bats (Bash Automated Testing System). Exemples concrets d'assertions, de vérifications de sortie et d'échecs.
Traduction automatique
Cet article a plus d'un an, mais il a été relu le 30 juillet 2026 — son contenu est toujours exact.
Cet article fait partie de la série Écrire de meilleurs scripts Bash :
- Bash - ASCII art
- Bash - Charger des variables d'environnement depuis un fichier
- Bash - Script pour ajouter des fonctionnalités de log à votre script
- Bash - Afficher sur la console et dans un fichier de log en même temps
- Linux - Comparer deux versions du même script
- Linux - Trier les fonctions dans un script Bash
- Linux - Générer de la documentation à partir de scripts Bash
- Linux - Tirez parti du nombre de CPU dont vous disposez ; lancez des jobs concurrents
- Linux - Utiliser une barre de progression dans vos scripts
- Linux - Scripts Bash - Lancer des tests unitaires avec bats/bats

Cet article propose une introduction complète aux tests unitaires de scripts Bash avec le framework bats-core. Il montre comment démarrer rapidement en lançant les tests dans un container Docker, sans configuration compliquée. Le guide couvre l'écriture de tests de base, l'utilisation des différents types d'assertions comme assert_output, assert_failure et assert_equal, et montre comment structurer des tests pour de vraies fonctions shell. Des sujets plus avancés comme le mocking de commandes, le test de sorties multilignes et colorées, la vérification du contenu de fichiers et l'usage des fonctions setup/teardown pour les fixtures sont également expliqués avec des exemples concrets.
Comme tous les développeurs qui vivent en ligne de commande, j'écris des scripts Bash sous Linux. Comme tout programmeur, je suis censé écrire des tests unitaires. Bon, je dois avouer que je n'en écris que rarement.
Il y a quelque temps, j'ai écrit un ensemble de scripts Bash qui forment une bibliothèque de fonctions (comme un framework) et là, l'intérêt d'avoir des tests unitaires est encore plus grand puisque ces fonctions sont censées être stables et servir de fondations à des scripts plus avancés. Deux habitudes rendent une telle bibliothèque testable dès le départ : limiter le nombre de paramètres des fonctions et documenter chaque fonction avec un bloc de doc.
Dans cet article, nous voyons comment écrire des tests unitaires pour des scripts Bash. Si vous cherchez l'équivalent en PHP, voyez Écrire des tests unitaires PHP avec Pest ; pour des tests fonctionnels / de bout en bout plutôt que des tests unitaires, j'ai aussi parlé de Behat, Cypress et des tests navigateur de Pest v4 ; et si vous préférez qu'on vous écrive une première version de la suite de tests, un LLM local peut générer les cas manquants.
Commençons par une petite mise en bouche
Comme toujours, créons d'abord un nouveau dossier pour jouer avec quelques exemples.
Lancez mkdir -p /tmp/bats && cd $_ pour créer un dossier temporaire et y entrer ; puis lancez code . pour démarrer VSCode et ouvrir le dossier.
Nous allons créer un fichier d'illustration simple, appelons-le tests/simple.bats, et copier/coller le contenu suivant :
#!/usr/bin/env bats
setup() { bats_load_library bats-support bats_load_library bats-assert}
@test "Asserting it's hello" { run echo "hello" assert_output "hello" assert_success}
@test "Simple.bats exists" { run ls simple.bats assert_output --regexp "[Ss]imple\.bats" assert_success}Ce fichier définit une suite de tests BATS (Bash Automated Testing System) qui valide le comportement de commandes shell simples. La fonction setup charge deux bibliothèques d'assertion et de support, puis deux tests vérifient respectivement qu'une commande echo retourne le bon texte et qu'un fichier existe avec le motif de nom attendu.
Et maintenant, la partie très difficile est, ah non, en fait vraiment facile, de lancer Bats-core :
Et... ça marche.

Qu'avons-nous fait ?
Nous avons créé un exemple très basique avec deux vérifications.
La première s'appelle Asserting it's hello et nous avons lancé echo "hello" exactement comme dans un script shell. Ensuite, en lançant assert_output "hello", nous vérifions que la sortie de la commande lancée est bien hello et c'est un succès (donc c'est hello et rien d'autre).
C'est fait avec ce script :
@test "Asserting it's hello" { run echo "hello" assert_output "hello" assert_success}Ce test vérifie qu'une commande echo retourne bien « hello » comme sortie et s'exécute sans erreur. Il utilise le framework bats pour exécuter et valider le comportement d'une commande shell simple.
Et la seconde vérification s'appelle Simple.bats exists : nous faisons simplement un ls simple.bats et vérifions si, pour l'illustration, Simple.bats ou simple.bats est retourné dans la liste des fichiers (ceci, pour illustrer l'usage d'une expression régulière).
@test "Simple.bats exists" { run ls simple.bats assert_output --regexp "[Ss]imple\.bats" assert_success}Ce test vérifie que le fichier simple.bats existe en lançant la commande ls et en s'assurant qu'elle réussit et affiche bien le nom du fichier (avec la première lettre en majuscule ou minuscule).
Vérifier un échec
Bien sûr, nous pouvons aussi vérifier un échec. Modifiez votre fichier simple.bats comme ceci :
#!/usr/bin/env bats
setup() { bats_load_library bats-support bats_load_library bats-assert}
@test "Asserting it's hello" { run echo "hello" assert_output "hello" assert_success}
@test "Simple.bats exists" { run ls simple.bats assert_output --regexp "[Ss]imple\.bats" assert_success}
# highlight-start@test "Removing an inexistent file" { run rm INEXISTENT_FILE assert_failure}# highlight-endCe fichier de tests Bats configure deux bibliothèques d'assertion (bats-support et bats-assert) au démarrage, puis définit trois tests : vérifier que echo affiche « hello », que le fichier simple.bats existe, et que la suppression d'un fichier inexistant échoue correctement.
Donc, dans la nouvelle fonction de test, nous essayons simplement de supprimer un fichier inexistant et nous attendons, forcément, un échec :

Quelques cas d'usage réels
Imaginez l'arborescence simplifiée suivante :
Le fichier src/assert.sh contient le code shell Linux que vous voulez tester. Vos scénarios de test doivent être stockés dans le dossier tests. Puisque nous allons écrire des tests pour le fichier src/assert.sh, créons tests/assert.bats.
Ci-dessous le contenu de src/assert.sh. Commençons par une fonction simple : vérifier si un binaire donné est installé sur le système ou non. Le nom du binaire doit être passé en paramètre à la fonction.
#!/usr/bin/env bash
function assert::notEmpty() { local -r variable="${1:-}" local -r errorMessage="${2:-"The variable can't be empty."}"
[[ -z ${variable} ]] && echo "${errorMessage}" >&2
return 0}
function assert::binaryExists() { local -r binary="${1:-}"
assert::notEmpty "${binary}" \ "You should specify the name of the program whose existence needs to be checked."
if [[ -z "$(command -v "$binary" || true)" ]]; then echo "The ${binary} binary wasn't found on your system." >&2 exit 1 fi
return 0}Ce script bash fournit deux fonctions d'assertion pour valider des conditions. La première vérifie qu'une variable n'est pas vide et affiche un message d'erreur personnalisé si elle l'est. La deuxième s'appuie sur la première pour vérifier qu'un programme binaire existe sur le système, et arrête complètement le script si ce n'est pas le cas.
Ci-dessous le contenu de tests/assert.bats.
#!/usr/bin/env bats
setup() { bats_load_library bats-assert bats_load_library bats-support
source ./../src/assert.sh
# Create a dummy file every time a test function is called tmpFile="/tmp/bats/assert.tmp" [[ ! -d "/tmp/bats" ]] && mkdir "/tmp/bats"
touch "${tmpFile}"}
# Once the test function has been called, remove the temporaryteardown() { [[ -f "${tmpFile}" ]] && rm -f "${tmpFile}" return 0}
@test "assert::binaryExists - Assert some binaries exists" { # clear is a native Linux command run assert::binaryExists "clear"
# We expect thus binaryExists return a zero exit code (=success) assert_success
# and no output message since it's successful assert_output ""
# Another test with the native ls command run assert::binaryExists "ls" assert_output "" assert_success}
@test "assert::binaryExists - Assert some binaries didn't exist" { run assert::binaryExists "fakeProgram"
# Since fakeProgram didn't exist, we expect binaryExists exit with code 1 assert_failure 1
# We also expect this, exact, error message assert_output "The fakeProgram binary wasn't found on your system."}Ce fichier de test bats vérifie le comportement d'une fonction assert::binaryExists qui contrôle si des commandes existent sur le système. Avant chaque test, setup() charge les bibliothèques de test et crée un fichier temporaire ; après chaque test, teardown() nettoie ce fichier. Les deux tests valident que la fonction retourne succès avec une sortie vide quand un binaire existe (comme 'clear' ou 'ls'), et échec avec un message d'erreur spécifique quand le binaire n'existe pas.
La ligne de commande :
En la lançant, nous attendons un succès pour binaryExists avec les commandes clear et ls (puisqu'elles sont bien installées sur notre système) et nous attendons un échec pour fakeProgram mais, puisque nous utilisons assert_failure, notre scénario de tests devrait passer.
Ajoutons une vérification pour voir si une image Docker spécifique existe déjà ou non sur le système :
#!/usr/bin/env bash
function assert::notEmpty() { local -r variable="${1:-}" local -r errorMessage="${2:-"The variable can't be empty."}"
[[ -z ${variable} ]] && echo "${errorMessage}" >&2
return 0}
function assert::binaryExists() { local -r binary="${1:-}"
assert::notEmpty "${binary}" \ "You should specify the name of the program whose existence needs to be checked."
if [[ -z "$(command -v "$binary" || true)" ]]; then echo "The ${binary} binary wasn't found on your system." >&2 exit 1 fi
return 0}
# highlight-startfunction assert::dockerImageExists() { local -r imageName="${1:-}" local -r errorMessage="${2:-The Docker image \"$imageName\" did not exist}"
assert::notEmpty "${imageName}" "You should specify the name of the Docker image whose existence needs to be checked."
# When the image exists, "docker images -q" will return his ID (f.i. `5eed474112e9`), an empty string otherwise if [[ "$(docker images -q "$imageName" 2> /dev/null)" == "" ]]; then echo "${errorMessage}" >&2 exit 1 fi
return 0}# highlight-endCe script bash définit trois fonctions de validation : assert::notEmpty vérifie qu'une variable contient une valeur, assert::binaryExists confirme qu'un programme existe sur le système, et assert::dockerImageExists s'assure qu'une image Docker est disponible localement. Chaque fonction affiche un message d'erreur personnalisable et sort avec le code 1 en cas d'échec.
et le fichier tests/assert.bats mis à jour :
#!/usr/bin/env bats
setup() { bats_load_library bats-assert bats_load_library bats-support
source ./../src/assert.sh
# Create a dummy file every time a test function is called tmpFile="/tmp/bats/assert.tmp" [[ ! -d "/tmp/bats" ]] && mkdir "/tmp/bats"
touch "${tmpFile}"}
# Once the test function has been called, remove the temporaryteardown() { [[ -f "${tmpFile}" ]] && rm -f "${tmpFile}" return 0}
@test "assert::binaryExists - Assert some binaries exists" { # clear is a native Linux command run assert::binaryExists "clear"
# We expect thus binaryExists return a zero exit code (=success) assert_success
# and no output message since it's successful assert_output ""
# Another test with the native ls command run assert::binaryExists "ls" assert_output "" assert_success}
@test "assert::binaryExists - Assert some binaries didn't exist" { run assert::binaryExists "FakeProgram"
# Since FakeProgram didn't exist, we expect binaryExists exit with code 1 assert_failure 1
# We also expect this, exact, error message assert_output "The FakeProgram binary wasn't found on your system."}
# highlight-start@test "assert::dockerImageExists - Assert docker image exists - using mockup" { # Mock - we'll create a very simple docker override and return a fake ID # This will simulate the `docker images -q "AN_IMAGE_NAME"` which return # the ID of the image when found docker() { echo "feb5d9fea6a5" }
run assert::dockerImageExists "A-great-Docker-Image" assert_output "" assert_success}
@test "assert::dockerImageExists - Assert docker image didn't exist - using mockup" { # Mock - we'll create a very simple docker override and return a fake ID # This will simulate the `docker images -q "AN_IMAGE_NAME"` which return # the ID of the image when found; here return an empty string to simulate # a non-existent image docker() { echo "" }
run assert::dockerImageExists "Fake/image" "ERROR - Bad choice, that image didn't exist" assert_failure 1 assert_output --partial "Bad choice, that image didn't exist"}# highlight-endCe fichier de test bats configure un environnement de test avec des bibliothèques d'assertions et un fichier temporaire, puis valide que les fonctions assert::binaryExists et assert::dockerImageExists fonctionnent correctement en testant à la fois les cas de succès et d'échec. Les tests pour Docker utilisent des mocks pour simuler le comportement de la commande docker sans dépendre d'une installation réelle.
Plus d'exemples
assert_equal
Vérifie simplement que les deux valeurs sont égales. Ici, nous appelons une fonction qui retourne la longueur d'un tableau et nous vérifions que c'est la valeur attendue.
@test "array::length - Calculate the length of an array" { arr=("one" "two" "three" "four" "five")
assert_equal $(array::length arr) 5}
function array::length() { local -n array_length=$1 echo ${#array_length[@]}}Ce fichier teste une fonction utilitaire array::length qui compte le nombre d'éléments dans un tableau. Le test crée un tableau de cinq chaînes de caractères et vérifie que la fonction retourne bien 5.
assert_failure
assert::binaryExists fera un exit 1 si le binaire ne peut pas être trouvé. Un message d'erreur du type The binary can't be found sera affiché sur la console.
@test "Assert binary didn't exist" { # Simulate which and return an error meaning "No, that binary didn't exist on the host" which() { exit 1 }
run assert::binaryExists "Inexistent_Binary" "Ouch, no, no, that binary didn't exist on the system"
assert_failure
assert_output --partial "Ouch, no, no, that binary didn't exist on the system"}
function assert::binaryExists() { local binary="${1}" local msg="${2:-${FUNCNAME[0]} - File \"$binary\" did not exist}"
[[ -z "$(which "$binary" || true)" ]] && echo "$msg" && exit 1
return 0}Ce test vérifie que la fonction assert::binaryExists détecte correctement l'absence d'un binaire sur le système. On simule une commande which qui échoue, puis on appelle la fonction avec un binaire inexistant et un message d'erreur personnalisé, en s'assurant que la fonction échoue et retourne le message attendu.
assert_output
assert_output a deux options bien pratiques : --partial et --regexp.
Utiliser --partial permet par exemple de vérifier que la sortie contient un texte brut donné, comme une phrase précise dans un écran d'aide.
@test "Show the help screen" { source 'src/helpers.sh'
arrArguments=("--help")
run helpers::showHelp ${arrArguments[@]}
assert_output --partial "Convert a revealJs slideshow to a PDF document"}Ce test vérifie que la fonction helpers::showHelp affiche correctement le message d'aide quand on lui passe l'argument --help. Le test charge d'abord le fichier helpers.sh, puis exécute la fonction avec cet argument et vérifie que la sortie contient le texte attendu.
Utiliser --regexp permet d'employer une expression régulière :
@test "Show the help screen" { source 'src/helpers.sh' arrArguments=("--input InvalidFile") run helpers::__process ${arrArguments[@]} assert_output --regexp "ERROR - The input file .* doesn't exist."}Ce test vérifie que la fonction helpers::__process affiche un message d'erreur approprié quand on lui passe un fichier d'entrée invalide. On construit un tableau d'arguments contenant --input InvalidFile, on appelle la fonction avec ces arguments, et on vérifie que la sortie correspond au motif d'erreur attendu.
assert_success
assert::binaryExists retourne 0 quand le binaire est trouvé. La fonction s'exécute en silence (aucune sortie).
@test "Assert binary exists" { run assert::binaryExists "clear" # clear is a native Linux command assert_output "" # No output when success assert_success}
function assert::binaryExists() { local binary="${1}" local msg="${2:-${FUNCNAME[0]} - File \"$binary\" did not exist}"
[[ -z "$(which "$binary" || true)" ]] && echo "$msg" && exit 1
return 0}Ce code teste une fonction utilitaire qui vérifie qu'une commande binaire existe sur le système. Le test appelle assert::binaryExists avec la commande « clear », s'attend à aucune sortie et à un code de succès, tandis que la fonction elle-même cherche le binaire dans le PATH et quitte avec une erreur si introuvable.
Vérifier les couleurs ANSI
Imaginez le code suivant :
__RED=31
function console::printRed() { for line in "$@"; do printf "\e[1;${__RED}m%s\e[0m\n" "$line" done}Ce code définit une constante de couleur ANSI rouge et une fonction qui affiche du texte en rouge gras dans le terminal. La fonction itère sur tous les arguments passés et les imprime chacun sur une nouvelle ligne avec le formatage couleur.
Nous voulons vérifier que la ligne sera affichée en rouge.
@test "console::printRed - The sentence should be displayed" { run console::printRed "This line should be echoed in Red" assert_output "�[1;31mThis line should be echoed in Red�[0m" assert_success}Ce test vérifie que la fonction console::printRed affiche correctement une chaîne de caractères en rouge dans la console. Il exécute la fonction avec un message test, puis valide que la sortie contient les codes ANSI pour le texte rouge avec contraste fort et qu'aucune erreur ne s'est produite.
Vérifier une sortie multiligne
Imaginez le code suivant :
function console::banner() { printf "%s\n" "============================================" printf "= %-40s =\n" "$@" printf "%s\n" "============================================"}Cette fonction affiche un message centré dans une boîte de caractères égaux, comme une bannière. Elle prend en paramètre le texte à afficher et l'entoure de lignes décoratives pour le mettre en évidence.
Cela va écrire trois lignes sur la console, comme par exemple :
# ============================================
# = Step 1 - Initialisation =
# ============================================
Pour vérifier plusieurs lignes, utilisez le tableau $lines comme ceci :
@test "console::banner - The sentence should be displayed" { run console::banner "Step 1 - Initialisation"
assert_equal "${lines[0]}" "============================================" assert_equal "${lines[1]}" "= Step 1 - Initialisation =" assert_equal "${lines[2]}" "============================================"
assert_success}Ce test vérifie que la fonction console::banner affiche correctement une bannière formatée avec des bordures en égales de part et d'autre du texte. Le test exécute la fonction avec un message et valide que chaque ligne du résultat correspond exactement au format attendu, puis vérifie que la commande s'est exécutée sans erreur.
Vérifier par rapport à un fichier
Imaginez une fonction qui va parser un fichier et, par exemple, supprimer certains paragraphes. Nous devons vérifier que le contenu est correct, une fois la fonction lancée.
Pour cela, imaginez une fonction removeTopOfFileComments. La fonction va parser le fichier et supprimer les commentaires HTML (<!-- ... -->) présents en haut du fichier.
Pour le test, nous allons créer un fichier avec trois lignes vides, puis un bloc de commentaire HTML, puis deux lignes vides, puis le code HTML. Donc, en supprimant le commentaire HTML, nous aurons cinq lignes vides suivies du bloc HTML : nous devons donc vérifier que notre fichier contient six lignes.
L'astuce utilisée est :
cat --show-ends --show-tabs "$tempfile"c'est-à-dire récupérer le contenu du fichier mais avec un$là où il y a un saut de ligne et, ici, aussi^Ipour les tabulations.- ensuite nous passons le résultat dans
tr "\n" "#": au lieu d'obtenir six lignes, nous n'en obtenons qu'une, en remplaçant les sauts de ligne par#.
Et voilà, puisque nous avons une variable avec une seule ligne (dans notre exemple : $#$#$#$#$#<html><body/></html>$#), nous pouvons la comparer à ce que nous attendons :
@test "html::removeTopOfFileComments - remove HTML comments - with empty lines" { tempfile="$(mktemp)"
# Here, we'll have extra, empty, lines. They should be removed too echo '' >$tempfile echo '' >>$tempfile echo '' >>$tempfile echo '<!-- ' >>$tempfile # We also add extra spaces before the start tag echo ' Lorem ipsum dolor sit amet, consectetur adipiscing elit.' >>$tempfile echo ' Morbi interdum elit a nisi facilisis pulvinar.' >>$tempfile echo ' Vestibulum fermentum consequat suscipit. Vestibulum id sapien metus.' >>$tempfile echo '--> ' >>$tempfile # We also add extra spaces after the end tag echo '' >>$tempfile echo '' >>$tempfile echo '<html><body/></html>' >>$tempfile
run html::removeTopOfFileComments "$tempfile"
# Get now the content of the file # We expect three empty lines (the three first) # The HTML comment has been removed # Then there are two more empty line (so we'll five empty lines) # And we'll have our "<html><body/></html>" block. # # cat --show-ends --show-tabs will show the dollar sign (end-of-line) and f.i. ^I for tabulations # tr "\n" "#" will then convert the line feed character to a hash so, in fact, fileContent will # be a string like `$#$#$#$#$#<html><body/></html>$#` fileContent="$(cat --show-ends --show-tabs "$tempfile" | tr "\n" "#")"
# Once we've our string, compare the fileContent with our expectation assert_equal "$fileContent" "\$#\$#\$#\$#\$#<html><body/></html>\$#"}Ce test vérifie qu'une fonction nommée html::removeTopOfFileComments supprime correctement les commentaires HTML situés au début d'un fichier, y compris les lignes vides qui les entourent. Le test crée un fichier temporaire contenant des lignes vides, un commentaire HTML avec espaces superflus, puis du contenu HTML valide, puis exécute la fonction et valide que seul le contenu HTML reste après suppression du commentaire et conservation des lignes vides.
Vérifier un fichier avec une regex
Un second scénario peut être : vous avez une fonction d'écriture (pensez à un fichier de log) et vous voulez vérifier la présence d'une ligne donnée dans le fichier.
L'exemple ci-dessous s'appuie sur bats-file et sa méthode assert_file_contains. Cette méthode demande un nom de fichier et un motif regex.
setup() { bats_load_library bats-support bats_load_library bats-assert bats_load_library bats-file
#! "grep" without the "-P" argument seems to not support repetition like "\d{4}" # # a date like `2022-04-07` regexDate="[0-9][0-9][0-9][0-9]\-[0-9][0-9]\-[0-9][0-9]" # a time like `17:41:22` regexTime="[0-9][0-9]\:[0-9][0-9]\:[0-9][0-9]" # a time zone difference like "0200" regexUTC="[0-9]*" #! Should be [0-9][0-9][0-9][0-9] but didn't work???
# The final pattern so we can match f.i. `[2022-04-07T18:00:20+0200] ` __DATE_PATTERN="\[${regexDate}\T${regexTime}\+${regexUTC}.*\]\s"
return 0}
@test "log::write - Write a line in the log" { local sentence="" sentence="This is my important message" run write "${sentence}"
assert_file_exist "/tmp/bats_log.tmp"
echo "${__DATE_PATTERN}${sentence}" >/tmp/regex.tmp assert_file_contains "/tmp/bats_log.tmp" "${__DATE_PATTERN}${sentence}" assert_success}Ce fichier configure un ensemble de tests BATS pour vérifier le fonctionnement d'une fonction log::write. La fonction setup() charge les bibliothèques d'assertion nécessaires et définit des patterns regex pour valider que les messages sont écrits dans un fichier journal avec un horodatage correct au format ISO 8601.
Vérifier qu'une valeur n'est PAS dans un fichier
Un autre usage de assert_failure peut être de lancer une commande comme un grep et de s'attendre à obtenir une erreur :
run grep "REGEX_SOMETHING_THAT_SHOULD_BE_MISSING" "/tmp/test.log"
assert_failure 1
Quelques fonctions spéciales
setup
La fonction setup est appelée avant de lancer un test. Pour chaque fonction @test présente dans le scénario, la fonction setup() sera appelée.
Dans l'exemple suivant, comme il y a deux fonctions de test, setup() sera appelée deux fois.
setup() { bats_load_library bats-support bats_load_library bats-assert
source 'src/env.sh'}
@test "env::assertFileExists - Assert .env file exists - The path isn't initialised" { run env::assertFileExists assert_failure}
@test "env::assertFileExists - Assert .env file exists - The file exists" { ENV_ROOT_DIR="/tmp" ENV_FILENAME=".env.bats.testing" touch ${ENV_ROOT_DIR}/${ENV_FILENAME} run env::assertFileExists assert_success}Ce fichier de test BATS configure l'environnement de test en chargeant les bibliothèques d'assertion, puis teste une fonction env::assertFileExists qui vérifie l'existence d'un fichier .env. Les deux cas de test couvrent le comportement quand le chemin n'est pas initialisé (devrait échouer) et quand le fichier existe (devrait réussir).
teardown
Tout comme setup, la fonction teardown sera appelée pour chaque test mais une fois le test lancé. C'est le bon endroit pour, par exemple, supprimer des fichiers créés pendant l'exécution d'un test.
teardown() {
rm -f /tmp/bats
}
Lancer les tests
La commande docker run --rm -it -w /code/tests -v .:/code bats/bats:latest simple.bats va lancer le fichier simple.bats tandis que docker run --rm -it -w /code/tests -v .:/code bats/bats:latest va lancer tous les fichiers .bats du répertoire de travail.
Mocking
Nous pouvons surcharger une fonction pendant un test. Prenez le cas d'usage suivant : nous avons une fonction qui retourne 0 quand une image Docker donnée est présente sur l'host. La fonction retourne 1 et affiche une erreur sur la console si l'image n'est pas trouvée.
function assert::dockerImageExists() { local image="${1}" local msg="${2:-The Docker image \"$image\" did not exist}"
# When the image exists, "docker images -q" will return his ID (f.i. `5eed474112e9`), an empty string otherwise [[ "$(docker images -q "$image" 2>/dev/null)" == "" ]] && echo "$msg" && exit 1
return 0}Cette fonction vérifie qu'une image Docker existe et arrête le script avec une erreur si ce n'est pas le cas. Elle prend le nom de l'image en premier paramètre et un message d'erreur optionnel en second, puis utilise docker images -q pour vérifier la présence de l'image.
Nous devons donc surcharger la réponse de docker. Quand l'image est censée être là, il suffit de retourner une chaîne non vide, n'importe quoi sauf une chaîne vide. Retournons un faux ID pour vraiment simuler la réponse de docker images -q.
@test "Assert docker image exists" { # Mock - we'll create a very simple docker override and return a fake ID # This will simulate the `docker images -q "AN_IMAGE_NAME"` which return # the ID of the image when found docker() { echo "feb5d9fea6a5" }
source assert.sh run assert::dockerImageExists "A-great-Docker-Image" assert_output "" # No output when success assert_success}Ce test vérifie que la fonction assert::dockerImageExists fonctionne correctement en simulant une image Docker existante. On remplace la commande docker par une version fictive qui retourne un ID d'image, puis on exécute la fonction d'assertion et on vérifie qu'elle réussit sans produire de sortie.
Et retournons une chaîne vide pour simuler une image inexistante.
@test "Assert docker image didn't exist" { # Mock - we'll create a very simple docker override and return a fake ID # This will simulate the `docker images -q "AN_IMAGE_NAME"` which return # the ID of the image when found; here return an empty string to simulate # an inexistent image docker() { echo "" }
source assert.sh run assert::dockerImageExists "Fake/image" "Bad choice, that image didn't exist" assert_output --partial "Bad choice, that image didn't exist" assert_failure}Ce test vérifie que la fonction assert::dockerImageExists signale correctement quand une image Docker n'existe pas. On simule une commande docker qui retourne vide (comme si l'image était absente), on appelle la fonction d'assertion avec un message d'erreur personnalisé, puis on vérifie que ce message s'affiche et que la fonction échoue.
Conclusion
Bats apporte à Bash ce qui lui a toujours manqué : un moyen de prouver qu'un script fait encore ce qu'il faisait le mois dernier. Le coût de mise en place est proche de zéro — un fichier .bats, l'image bats/bats, et aucun runner à installer — et le bénéfice apparaît la première fois qu'un refactoring casse une fonction que vous aviez oubliée.
Commencez petit. Prenez le script que vous avez le plus peur de toucher, écrivez trois tests pour son cas nominal, et ajoutez-en un chaque fois que vous y corrigez un bug. C'est généralement suffisant pour transformer un fichier Bash que vous n'osiez plus modifier en un fichier que vous pouvez modifier.
Cet article fait partie de la série Écrire de meilleurs scripts Bash :
- Bash - ASCII art
- Bash - Charger des variables d'environnement depuis un fichier
- Bash - Script pour ajouter des fonctionnalités de log à votre script
- Bash - Afficher sur la console et dans un fichier de log en même temps
- Linux - Comparer deux versions du même script
- Linux - Trier les fonctions dans un script Bash
- Linux - Générer de la documentation à partir de scripts Bash
- Linux - Tirez parti du nombre de CPU dont vous disposez ; lancez des jobs concurrents
- Linux - Utiliser une barre de progression dans vos scripts
- Linux - Scripts Bash - Lancer des tests unitaires avec bats/bats
Chargement des commentaires…




