Tester les fonctions avec Bats-core
Structurez les fichiers de test, les assertions et la préparation et le nettoyage afin de vérifier les fonctions Bash individuellement.
Tester les fonctions avec Bats-core est une leçon Linux Command Line & Bash Scripting Mastery gratuite sur CoddyKit. Ceci est la leçon 1 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Linux Command Line & Bash Scripting Mastery, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Linux Command Line & Bash Scripting Mastery comprend 4 leçons au total.
Qu’est-ce que Bats-core et pourquoi l’utiliser ?
Bats-core (Bash Automated Testing System) est le framework de référence pour les tests unitaires de Bash. Il vous permet d’écrire des tests structurés et reproductibles pour vos fonctions et scripts shell, de la même manière que vous utiliseriez JUnit pour Java ou pytest pour Python.
- Chaque test est un bloc
@testaccompagné d’une description compréhensible. - Les tests réussissent lorsque chaque commande qu’ils contiennent renvoie le code de sortie
0. - Les tests échouent dès qu’un code de sortie différent de zéro est renvoyé ou qu’une assertion échoue.
- La sortie est compatible avec TAP : les systèmes d’intégration continue (GitHub Actions, Jenkins, GitLab CI) la comprennent nativement.
Installez-le avec votre gestionnaire de paquets ou clonez le dépôt :
# 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.yVotre premier fichier de test Bats
Un fichier de test Bats porte l’extension .bats et commence par un shebang spécial. L’élément de base est la directive @test, suivie d’une chaîne de description et d’un bloc de commandes.
- Le shebang
#!/usr/bin/env batsindique au shell comment exécuter le fichier. - Chaque bloc
@testconstitue un cas de test indépendant. - Vous pouvez exécuter un seul fichier avec
bats my_tests.batsou un répertoire entier avecbats test/.
Voici la structure minimale d’un fichier de test 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
}Charger la fonction testée avec « load »
Dans les projets réels, vos fonctions Bash se trouvent dans des fichiers de bibliothèque, et non directement dans le fichier de test. Bats fournit l’assistant load, qui permet de charger des fichiers externes relativement au répertoire du fichier de test.
load '../lib/math.sh'charge le fichier avant l’exécution de chaque test.- Après le chargement, toutes les fonctions définies dans ce fichier sont disponibles dans vos blocs de test.
- Conservez vos fonctions de bibliothèque dans un répertoire
lib/et vos tests dans un répertoiretest/afin de bien les séparer.
Exemple de structure de projet et du test correspondant :
# 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" ]
}Assertions essentielles : run, $status, $output
La commande run est au cœur des tests Bats. Au lieu d’exécuter directement une commande, vous pouvez l’entourer de run pour capturer son code de sortie et sa sortie, sans provoquer l’échec immédiat du test.
$status— contient le code de sortie de la dernière commanderun.$output— contient la sortie standard combinée de la dernière commanderun.$lines— tableau dont chaque élément correspond à une ligne de sortie (${lines[0]},${lines[1]}, etc.).
Vous pouvez ainsi vérifier les cas de réussite comme les cas d’échec :
#!/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"* ]]
}Utiliser bats-assert pour des assertions explicites
Les assertions intégrées [ ] fonctionnent, mais leurs messages d’échec sont peu instructifs. La bibliothèque d’assistants bats-assert fournit des fonctions d’assertion explicites qui indiquent précisément ce qui s’est mal passé.
assert_success— vérifie que$statusvaut 0.assert_failure— vérifie que$statusest différent de zéro.assert_output— vérifie que$outputest égal à la chaîne fournie.assert_output --partial— vérifie que la sortie contient la sous-chaîne.refute_output --partial— vérifie que la sortie ne contient PAS la sous-chaîne.
Installez-la en clonant bats-core/bats-assert dans un répertoire test/helpers/, puis chargez-la :
#!/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 et teardown : points d’accroche du cycle de vie des tests
Bats fournit deux fonctions spéciales — setup et teardown — qui s’exécutent automatiquement autour de chaque test. Utilisez-les pour préparer et nettoyer l’état partagé, afin que chaque test démarre dans un environnement connu.
setup()s’exécute avant chaque bloc@test.teardown()s’exécute après chaque bloc@test, même si le test échoue.- Utilisations courantes : créer des répertoires temporaires, définir des variables d’environnement et supprimer les fichiers temporaires après le test.
#!/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 et teardown_file : points d’accroche au niveau de la suite
Il suffit parfois de configurer des ressources coûteuses une seule fois par fichier, et non avant chaque test. Bats fournit setup_file et teardown_file à cette fin.
setup_file()s’exécute une fois avant tous les tests du fichier.teardown_file()s’exécute une fois après tous les tests du fichier.- Utilisez
BATS_FILE_TMPDIR(disponible automatiquement) pour partager des données entresetup_fileet vos tests : les variables ordinaires ne sont pas conservées entre les sous-shells.
Cas d’utilisation courant : démarrer un serveur simulé ou compiler un binaire une seule fois, puis l’arrêter à la fin :
#!/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"
}Tester les fonctions qui modifient des fichiers
Il est très courant de tester des fonctions Bash qui lisent ou écrivent dans le système de fichiers. La technique essentielle consiste à utiliser des répertoires temporaires (avec mktemp -d dans setup) afin que les tests ne touchent jamais aux vrais fichiers et n’interfèrent jamais les uns avec les autres.
- Travaillez toujours dans
$TEST_DIR(ou$BATS_TEST_TMPDIR, disponible automatiquement dans les versions récentes de Bats). - Utilisez la bibliothèque d’assistants
bats-filepour effectuer simplement des vérifications sur les fichiers, commeassert_file_existsetassert_file_contains. - Ne codez jamais en dur des chemins comme
/tmp/myfile: les exécutions parallèles des tests entreraient en conflit.
#!/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"
}Simuler des commandes externes
Les fonctions appellent souvent des programmes externes comme curl, aws ou git. Dans les tests unitaires, vous voulez tester votre logique, et non la véritable commande externe. La technique de simulation la plus simple avec Bats consiste à définir dans setup une fonction shell portant le même nom que la commande : elle prend le pas sur le véritable binaire.
- Définissez dans
setupune fonction telle quecurl() { echo 'mocked response'; return 0; }, puis exportez-la. - Utilisez
export -f curlpour que la fonction soit visible dans les sous-shells créés parrun. - Vous pouvez également écrire la simulation dans un fichier temporaire placé dans le
PATHpour les scénarios plus complexes.
#!/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"
}Ignorer et étiqueter les tests
Il n’est pas toujours possible d’exécuter tous les tests : vous avez parfois besoin d’une véritable connexion réseau, d’un outil précis ou d’un système d’exploitation particulier. Bats fournit skip pour ignorer conditionnellement un test avec un message explicite, plutôt que de le commenter ou de provoquer l’échec de la suite.
- Appelez
skip "reason"n’importe où dans un bloc@testpour ignorer ce test. - Les tests ignorés apparaissent dans la sortie sous la forme
Set ne sont pas comptabilisés comme des échecs. - Bats 1.5+ prend en charge les étiquettes : annotez les tests avec
# bats test_tags=slow,networket filtrez-les avecbats --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/Structurer une suite de tests complète
Un projet Bats bien organisé suit une structure de répertoires prévisible, ce qui facilite l’intégration des nouveaux contributeurs et l’intégration aux chaînes d’intégration continue.
Structure recommandée :
lib/— fonctions Bash de production (un fichier par fonctionnalité :math.sh,fileutils.sh).test/— un fichier.batspar fichier de bibliothèque (math.bats,fileutils.bats).test/helpers/— bats-assert, bats-file et bats-support en tant que sous-modules git.Makefile— une cibletestpour que les contributeurs n’aient plus qu’à exécutermake test.
Exécutez toute la suite avec une seule commande :
# 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
# `-- MakefileVérification des connaissances : assertions Bats-core
Vérifiez votre compréhension du mécanisme central de test de Bats-core.
Récapitulatif : tester Bash avec Bats-core
Dans cette leçon, vous avez appris à structurer et à écrire des tests unitaires pour des fonctions Bash individuelles avec Bats-core. Voici les points essentiels à retenir :
- Structure des fichiers de test — utilisez le shebang
#!/usr/bin/env batset des blocs@testportant des noms explicites. load— chargez vos fichiers de bibliothèque afin que les fonctions soient disponibles dans les tests, sans copier-coller.run+$status+$output— le trio essentiel ; utilisez toujoursrunpour capturer les résultats sans provoquer l’échec immédiat du test.- bats-assert — préférez
assert_success,assert_failureetassert_outputaux crochets bruts[ ]pour obtenir des messages d’échec lisibles. setup/teardown— s’exécutent avant et après chaque test ;setup_file/teardown_files’exécutent une fois par fichier.- Simulation — remplacez les commandes externes par des fonctions shell portant le même nom et exportées avec
export -f. skip— ignorez conditionnellement les tests qui dépendent de ressources indisponibles.- Structure du projet — séparez
lib/,test/ettest/helpers/pour faciliter la maintenance et l’intégration continue.
Ces pratiques donnent à vos projets Bash la même rigueur de test que celle que vous appliqueriez à tout projet logiciel moderne.
Questions Fréquemment Posées
La leçon « Tester les fonctions avec Bats-core » est-elle gratuite ?
Oui — le texte complet de « Tester les fonctions avec Bats-core » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Linux Command Line & Bash Scripting Mastery, passe à CoddyKit PRO. Le cours Linux Command Line & Bash Scripting Mastery comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Tester les fonctions avec Bats-core » ?
Structurez les fichiers de test, les assertions et la préparation et le nettoyage afin de vérifier les fonctions Bash individuellement. Tu pratiques Linux Command Line & Bash Scripting Mastery avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Linux Command Line & Bash Scripting Mastery ?
Aucune expérience préalable n'est requise. Linux Command Line & Bash Scripting Mastery sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 1 sur 4.
Combien de temps prend la leçon « Tester les fonctions avec Bats-core » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Linux Command Line & Bash Scripting Mastery ?
Oui. Chaque leçon Linux Command Line & Bash Scripting Mastery inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Tester les fonctions avec Bats-core
- Simuler des commandes et créer des doublures d’outils externes
- Dispositifs de test, environnements temporaires et couverture
- Exécuter des tests Shell dans les pipelines CI