Tests Bash
Frameworks et motifs de test pour les scripts shell, axés sur shunit2 et shellspec.
Sélection du Framework
| Framework | Points Forts | Meilleur Pour |
|---|---|---|
| shunit2 | Style xUnit, simple, portable | Tests unitaires, tests de fonctions |
| shellspec | Style BDD, moderne, étendu | Spécifications de comportement, projets complets |
shunit2
Installation
# Téléchargement direct
curl -L https://github.com/kward/shunit2/raw/master/shunit2 -o shunit2
# Ou via le gestionnaire de paquets
apt install shunit2
brew install shunit2
Structure de Base d'un Test
#!/usr/bin/env bash
# Source du script à tester
source ./my_script.sh
# Les fonctions de test commencent par test
test_addition() {
result=$(add 2 3)
assertEquals "2 + 3 devrait être égal à 5" "5" "$result"
}
test_string_output() {
result=$(greet "World")
assertEquals "Hello, World" "$result"
}
test_file_creation() {
create_temp_file
assertTrue "Le fichier temporaire devrait exister" "[ -f /tmp/testfile ]"
}
test_exit_code() {
validate_input "valide"
assertEquals "Devrait retourner 0 pour une entrée valide" 0 $?
}
# Configuration et démontage
oneTimeSetUp() {
# Exécuté une fois avant tous les tests
export TEST_DIR=$(mktemp -d)
}
oneTimeTearDown() {
# Exécuté une fois après tous les tests
rm -rf "$TEST_DIR"
}
setUp() {
# Exécuté avant chaque test
cd "$TEST_DIR"
}
tearDown() {
# Exécuté après chaque test
rm -f "$TEST_DIR"/*
}
# Chargement de shunit2
source shunit2
Assertions shunit2
# Égalité
assertEquals [message] attendu réel
assertNotEquals [message] inattendu réel
# Null/Vide
assertNull [message] valeur
assertNotNull [message] valeur
# Booléen/Statut
assertTrue [message] condition
assertFalse [message] condition
# Même référence (comparaison de chaînes)
assertSame [message] attendu réel
assertNotSame [message] inattendu réel
# Contient (dans une chaîne)
assertContains [message] conteneur contenu
# Code de sortie
assertEquals 0 $?
Tester les Fonctions de Manière Isolée
#!/usr/bin/env bash
# test_my_functions.sh
source ./my_functions.sh
test_calculate_sum_empty() {
result=$(calculate_sum)
assertEquals "Une somme vide devrait être 0" "0" "$result"
}
test_calculate_sum_single() {
result=$(calculate_sum 5)
assertEquals "5" "$result"
}
test_calculate_sum_multiple() {
result=$(calculate_sum 1 2 3 4 5)
assertEquals "15" "$result"
}
test_validate_email_valid() {
validate_email "[email protected]"
assertTrue "Un email valide devrait passer" $?
}
test_validate_email_invalid() {
validate_email "pas-un-email"
assertFalse "Un email invalide devrait échouer" $?
}
source shunit2
shellspec
Installation
# Via curl
curl -fsSL https://git.io/shellspec | sh
# Via Homebrew
brew install shellspec
# Via les gestionnaires de paquets
apt install shellspec
Structure de Répertoire
project/
├── lib/
│ └── functions.sh
├── spec/
│ ├── spec_helper.sh
│ ├── functions_spec.sh
│ └── support/
│ └── fixtures/
└── .shellspec
Configuration (.shellspec)
--require spec_helper
--format documentation
--color
Structure de Base d'une Spécification
Matchers shellspec
# Matchers de sortie
The output should eq "correspondance exacte"
The output should include "partiel"
The output should start with "préfixe"
The output should end with "suffixe"
The output should match pattern "*glob*"
The output should be blank
# Matchers de statut
The status should be success # sortie 0
The status should be failure # sortie non nulle
The status should eq 1 # code spécifique
# Matchers de variables
The variable VAR should eq "valeur"
The variable VAR should be defined
The variable VAR should be undefined
# Matchers de fichiers
The file "chemin" should be exist
The file "chemin" should be file
The file "chemin" should be directory
The file "chemin" should be readable
# Matchers de chemins
The path "fichier.txt" should be exist
Mocking avec shellspec
Describe 'fonction deploy'
# Mock d'une commande externe
curl() {
echo "réponse simulée"
return 0
}
It 'appelle le point de terminaison API'
When call deploy "serveur"
The output should include "réponse simulée"
End
End
Describe 'opérations sur fichiers'
# Mock avec remplacement de fonction
Mock rm
echo "rm appelé avec : $*"
End
It 'tente de supprimer le fichier'
When call cleanup_temp
The output should include "rm appelé avec"
End
End
Aide de Spécification
# spec/spec_helper.sh
# Chargement des fonctions communes
spec_helper_precheck() {
minimum_version "0.28.0"
}
spec_helper_loaded() {
# Configuration de l'environnement de test
export TEST_MODE=true
}
spec_helper_configure() {
# Importation des fonctions du projet
import 'lib/functions.sh'
}
Motifs de Test
Tester les Codes de Sortie
# shunit2
test_success_exit() {
run_command "entrée_valide"
assertEquals 0 $?
}
test_error_exit() {
run_command "entrée_invalide"
assertEquals 1 $?
}
# shellspec
It 'sort avec 0 en cas de succès'
When call run_command "entrée_valide"
The status should eq 0
End
Tester stdout et stderr
# shunit2
test_stdout() {
result=$(my_function 2>/dev/null)
assertEquals "sortie attendue" "$result"
}
test_stderr() {
error=$(my_function 2>&1 >/dev/null)
assertContains "$error" "message d'erreur"
}
# shellspec
It 'envoie la sortie sur stdout'
When call my_function
The stdout should eq "sortie attendue"
End
It 'envoie l'erreur sur stderr'
When call my_function
The stderr should include "message d'erreur"
End
Tester avec des Fixtures
# Configuration des fixtures de test
setUp() {
TEST_DIR=$(mktemp -d)
cat > "$TEST_DIR/config.json" <<EOF
{
"clé": "valeur"
}
EOF
}
tearDown() {
rm -rf "$TEST_DIR"
}
test_config_parsing() {
result=$(parse_config "$TEST_DIR/config.json")
assertEquals "valeur" "$result"
}
Tester les Fonctions Interactives
# Fournir une entrée via heredoc
test_interactive() {
result=$(my_prompt <<EOF
o ui
EOF
)
assertEquals "confirmé" "$result"
}
# Ou utiliser printf
test_with_input() {
result=$(printf 'oui\n' | my_prompt)
assertEquals "confirmé" "$result"
}
Exécution des Tests
# shunit2
./test_script.sh
# shellspec
shellspec # Exécuter toutes les spécifications
shellspec spec/fichier_spec.sh # Exécuter une spécification spécifique
shellspec --format tap # Sortie TAP
shellspec --jobs 4 # Exécution parallèle
Meilleures Pratiques
- Une assertion par test lorsque c'est possible
- Noms de test explicites expliquant ce qui est testé
- Isoler les tests - pas de dépendances entre les tests
- Tester les cas limites - entrée vide, caractères spéciaux, grandes données
- Nettoyer les ressources dans tearDown
- Simuler les commandes externes - ne testez pas curl, testez votre logique
- Tester les codes de sortie pas seulement la sortie standard