Scripts bash de test unitaire


112

Nous avons un système qui a des scripts bash en plus du code Java. Puisque nous essayons de tester tout ce qui pourrait éventuellement casser, et que ces scripts bash peuvent casser, nous voulons les tester.

Le problème est qu'il est difficile de tester les scripts bash.

Existe-t-il un moyen ou une meilleure pratique pour tester les scripts bash? Ou devrions-nous cesser d'utiliser des scripts bash et rechercher des solutions alternatives testables?




Vue d'ensemble des outils existants: medium.com/wemake-services/…
sobolevn

Réponses:


48

Il existe en fait un shunit2 , un framework de test unitaire basé sur xUnit pour les scripts shell basés sur Bourne. Je ne l'ai pas utilisé moi-même, mais cela vaut peut-être la peine d'être vérifié.

Des questions similaires ont déjà été posées:


3
Je peux affirmer (jeu de mots) que shunit2 (version 2.1.6) est un peu cassé à ce jour. AssertNull et assertNotNull ne fonctionnent pas, même si vous leur fournissez des valeurs directes. assertEquals fonctionne bien, mais je pense que je vais juste devoir rouler le mien pour le moment.
labyrinthe

@labyrinth, êtes-vous sûr que le problème n'était pas un cas de ceci: github.com/kward/shunit2/issues/53 "Comment utiliser assertNull correctement?"?
Victor Sergienko

1
@Victor Il est certainement possible que je n'ai pas été assez prudent avec mes guillemets. Je reviens bientôt dans un rôle où shunit2 ou un système de test unitaire bash sera très utile. Je vais essayer à nouveau.
labyrinthe

5
Je suis un utilisateur et parfois un contributeur de shunit2, et je peux confirmer que le projet est bien vivant en 2019.
Alex Harvey

31

J'ai obtenu la réponse suivante d'un groupe de discussion:

il est possible d'importer (inclure, peu importe) une procédure (fonction, quel que soit son nom) à partir d'un fichier externe. C'est la clé de l'écriture d'un script de test: vous divisez votre script en procédures indépendantes qui peuvent ensuite être importées à la fois dans votre script en cours d'exécution et dans votre script de test, puis vous avez votre script en cours d'exécution aussi simple que possible.

Cette méthode ressemble à l'injection de dépendances pour les scripts et semble raisonnable. Il est préférable d'éviter les scripts bash et d'utiliser un langage plus testable et moins obscur.


4
Je ne sais pas si je devrais voter pour ou contre, d'une part, la division en parties plus petites est bonne, mais d'autre part, j'ai besoin d'un cadre et non d'un ensemble de scripts personnalisés
mpapis

10
Bien qu'il n'y ait rien de mal avec bash (j'ai écrit beaucoup, beaucoup de scripts), c'est un langage difficile à maîtriser. Ma règle de base est que si un script est suffisamment volumineux pour nécessiter des tests, vous devriez probablement passer à un langage de script qui est facilement testé.
Doug

1
Mais parfois, vous avez besoin de quelque chose qui peut provenir du shell d'un utilisateur. Je ne sais pas comment vous feriez cela sans avoir recours à un script shell
Itkovian

@Itkovian - vous pouvez, par exemple, utiliser npm pour exporter un exécutable vers le chemin, donc aucun sourcing n'est nécessaire (votre package npm devra être installé globalement)
Eliran Malka

1
Je vais suivre les conseils de ne pas utiliser bash. :)
Maciej Wawrzyńczuk

30

Test Bash conforme au TAP : système de test automatisé Bash

TAP, le protocole Test Anything, est une interface textuelle simple entre les modules de test d'un faisceau de test. TAP a commencé sa vie dans le cadre du harnais de test pour Perl, mais a maintenant des implémentations en C, C ++, Python, PHP, Perl, Java, JavaScript et autres.


14
Cela vaut la peine de divulguer ce qu'est TAP et pourquoi devrait-on s'en soucier, sinon c'est juste un copier-coller sans signification
om-nom-nom

@ om-nom-nom: je l'ai lié au site TAP maintenant.
Janus Troelsen

7
Puisque personne d'autre ne disait l'indicible: TAP = Test Anything Protocol
JW.

9

Nikita Sobolev a écrit un excellent article de blog comparant quelques frameworks de test bash différents: Test des applications Bash

Pour les impatients: la conclusion de Nikita était d'utiliser Bats mais il semble que Nikita a raté le projet Bats-core qui me semble être celui à utiliser à l'avenir car le projet Bats original n'a pas été activement maintenu depuis 2013.


7

Epoxy est un framework de test Bash que j'ai conçu principalement pour tester d'autres logiciels, mais je l'utilise également pour tester des modules bash, y compris lui - même et Carton .

Les principaux avantages sont une surcharge de codage relativement faible, une imbrication d'assertions illimitée et une sélection flexible d'assertions à vérifier.

J'ai fait une présentation en le comparant à BeakerLib - un framework utilisé par certains chez Red Hat.


6

Pourquoi dites-vous qu'il est "difficile" de tester les scripts bash?

Quel est le problème avec les wrappers de test comme:

 #!/bin/bash
 set -e
 errors=0
 results=$($script_under_test $args<<ENDTSTDATA
 # inputs
 # go
 # here
 #
 ENDTSTDATA
 )
 [ "$?" -ne 0 ] || {
     echo "Test returned error code $?" 2>&1
     let errors+=1
     }

 echo "$results" | grep -q $expected1 || {
      echo "Test Failed.  Expected $expected1"
      let errors+=1
 }
 # and so on, et cetera, ad infinitum, ad nauseum
 [ "$errors" -gt 0 ] && {
      echo "There were $errors errors found"
      exit 1
 }

4
Premièrement, les scripts bash ne sont pas très lisibles. Deuxièmement, les attentes sont compliquées, comme vérifier si un fichier de verrouillage est créé avec le PID du script bash qui l'a créé.
nimcap

10
Plus important encore, il est difficile de tester les scripts shell car ils ont généralement un grand nombre d'effets secondaires et utilisent des ressources système telles que le système de fichiers, le réseau, etc. Idéalement, les tests unitaires sont sans effets secondaires et ne dépendent pas des ressources système.
jayhendren

4

J'ai créé shellspec parce que je voulais un outil facile à utiliser et utile.

Il a été écrit par un script shell POSIX pur. Il a testé avec de nombreux obus plus que shunit2. Il a des fonctionnalités puissantes que les chauves-souris / chauves-souris.

Par exemple, prise en charge du bloc imbriqué, facile à simuler / stub, facile à ignorer / en attente, tests paramétrés, numéro de ligne d'assertion, exécution par numéro de ligne, exécution parallèle, exécution aléatoire, formateur TAP / JUnit, couverture et intégration CI, profileur, etc. .

Voir la démo sur la page du projet.


3

J'aime assez shell2junit , un utilitaire pour générer une sortie de type JUnit à partir de tests de script Bash. Ceci est utile car le rapport généré peut ensuite être lu par des systèmes d'intégration continue, tels que les plug-ins JUnit pour Jenkins et Bamboo.

Bien que shell2junit ne fournisse pas le cadre de script Bash complet comme shunit2 , il vous permet d'avoir de bons rapports sur les résultats des tests.


3

Essayez bashtest . C'est un moyen simple de tester vos scripts. Par exemple, vous avez do-some-work.shqui modifie certains fichiers de configuration. Par exemple, ajoutez une nouvelle ligne PASSWORD = 'XXXXX'au fichier de configuration /etc/my.cfg.

Vous écrivez des commandes bash ligne par ligne, puis vérifiez la sortie.

Installer:

pip3 install bashtest

Créer des tests est une simple écriture de commandes bash.

Fichier test-do-some-work.bashtest:

# run the script  
$ ./do-some-work.sh > /dev/null

# testing that the line "PASSWORD = 'XXXXX'" is in the file /etc/my.cfg   
$ grep -Fxq "PASSWORD = 'XXXXX'" /etc/my.cfg && echo "YES"
YES

Exécutez les tests:

bashtest *.bashtest

Vous pouvez trouver quelques exemples ici et ici


3

Peut-être que cela peut être utilisé ou contribué à

https://thorsteinssonh.github.io/bash_test_tools/

Destiné à écrire des résultats dans le protocole TAP qui, j'imagine, est bon pour CI, et bon pour ceux qui veulent des environnements shell. J'imagine que certaines choses fonctionnent dans des environnements shell, donc certains pourraient dire qu'ils devraient être testés dans leur environnement shell.


3

Essayez d' assert.sh

source "./assert.sh"

local expected actual
expected="Hello"
actual="World!"
assert_eq "$expected" "$actual" "not equivalent!"
# => x Hello == World :: not equivalent!

J'espère que ça aide!


3

Je ne peux pas croire que personne ne parle d' OSHT ! Il est compatible avec les deux TAP et JUnit, il est shell pur (qui est, pas d' autres langues concernées), il fonctionne de façon autonome aussi, et il est simple et direct.

Le test ressemble à ceci (extraits tirés de la page du projet):

#!/bin/bash
. osht.sh

# Optionally, indicate number of tests to safeguard against abnormal exits
PLAN 13

# Comparing stuff
IS $(whoami) != root
var="foobar"
IS "$var" =~ foo
ISNT "$var" == foo

# test(1)-based tests
OK -f /etc/passwd
NOK -w /etc/passwd

# Running stuff
# Check exit code
RUNS true
NRUNS false

# Check stdio/stdout/stderr
RUNS echo -e 'foo\nbar\nbaz'
GREP bar
OGREP bar
NEGREP . # verify empty

# diff output
DIFF <<EOF
foo
bar
baz
EOF

# TODO and SKIP
TODO RUNS false
SKIP test $(uname -s) == Darwin

Une course simple:

$ bash test.sh
1..13
ok 1 - IS $(whoami) != root
ok 2 - IS "$var" =~ foo
ok 3 - ISNT "$var" == foo
ok 4 - OK -f /etc/passwd
ok 5 - NOK -w /etc/passwd
ok 6 - RUNS true
ok 7 - NRUNS false
ok 8 - RUNS echo -e 'foo\nbar\nbaz'
ok 9 - GREP bar
ok 10 - OGREP bar
ok 11 - NEGREP . # verify empty
ok 12 - DIFF <<EOF
not ok 13 - TODO RUNS false # TODO Test Know to fail

Le dernier test indique "pas ok", mais le code de sortie est 0 car c'est un TODO. On peut également définir verbose:

$ OSHT_VERBOSE=1 bash test.sh # Or -v
1..13
# dcsobral \!= root
ok 1 - IS $(whoami) != root
# foobar =\~ foo
ok 2 - IS "$var" =~ foo
# \! foobar == foo
ok 3 - ISNT "$var" == foo
# test -f /etc/passwd
ok 4 - OK -f /etc/passwd
# test \! -w /etc/passwd
ok 5 - NOK -w /etc/passwd
# RUNNING: true
# STATUS: 0
# STDIO <<EOM
# EOM
ok 6 - RUNS true
# RUNNING: false
# STATUS: 1
# STDIO <<EOM
# EOM
ok 7 - NRUNS false
# RUNNING: echo -e foo\\nbar\\nbaz
# STATUS: 0
# STDIO <<EOM
# foo
# bar
# baz
# EOM
ok 8 - RUNS echo -e 'foo\nbar\nbaz'
# grep -q bar
ok 9 - GREP bar
# grep -q bar
ok 10 - OGREP bar
# \! grep -q .
ok 11 - NEGREP . # verify empty
ok 12 - DIFF <<EOF
# RUNNING: false
# STATUS: 1
# STDIO <<EOM
# EOM
not ok 13 - TODO RUNS false # TODO Test Know to fail

Renommez-le pour utiliser une .textension et placez-le dans un tsous - répertoire, et vous pouvez utiliser prove(1)(une partie de Perl) pour l'exécuter:

$ prove
t/test.t .. ok
All tests successful.
Files=1, Tests=13,  0 wallclock secs ( 0.03 usr  0.01 sys +  0.11 cusr  0.16 csys =  0.31 CPU)
Result: PASS

Définir OSHT_JUNITou transmettre -jpour produire une sortie JUnit. JUnit peut également être combiné avec prove(1).

J'ai utilisé cette bibliothèque à la fois pour tester les fonctions en recherchant leurs fichiers, puis en exécutant des assertions avec IS/ OKet leurs négatifs, et des scripts en utilisant RUN/ NRUN. Pour moi, ce cadre offre le plus de gain pour le moins de frais généraux.


1

J'ai essayé beaucoup de solutions présentées ici, mais j'ai trouvé la plupart d'entre elles encombrantes et difficiles à utiliser, j'ai donc créé mon propre petit cadre de test: https://github.com/meonlol/t-bash

C'est juste un fichier dans le dépôt que vous pouvez simplement exécuter directement, avec un ensemble de base d'assertions de style JUnit.

Je l'ai utilisé professionnellement dans plusieurs projets internes et j'ai pu rendre nos scripts bash super stables et résistants à la régression.



0

Jetez un œil à Outthentic , il est simple, extensible par de nombreux langages (Perl, Python, Ruby, Bash au choix) et un framework multiplateforme (Linux, Windows) pour tester toutes les applications en ligne de commande.


-2

J'ai eu du mal à justifier l'utilisation de bash pour des scripts plus volumineux lorsque Python présente d'énormes avantages:

  • Try / Except permet d'écrire des scripts plus robustes avec la possibilité d'annuler les modifications en cas d'erreur.
  • Vous n'êtes pas obligé d'utiliser une syntaxe obscure telle que « if [ x"$foo" = x"$bar"]; then ...» qui est sujette à des erreurs.
  • Analyse facile des options et des arguments à l'aide du getoptmodule (et il existe un module encore plus simple pour analyser les arguments, mais le nom m'échappe).
  • Python vous permet de travailler avec des listes / dictées et des objets au lieu de chaînes de base et de tableaux.
  • Accès aux outils de langage appropriés tels que les expressions régulières, les bases de données (bien sûr, vous pouvez tout mysqldiriger vers la commande dans bash, mais ce n'est pas la meilleure façon d'écrire du code).
  • Pas besoin de s'inquiéter de l'utilisation de la forme correcte de $*ou "$*"ou "$@"ou $1ou "$1", les espaces dans les noms de fichiers ne sont pas un problème, etc., etc., etc.

Maintenant, je n'utilise bash que pour le plus simple des scripts.


3
Sans nier le fait que Python a des avantages mais votre deuxième point n'est pas très bien posé. La même comparaison aurait pu être faite if [[ $foo = $bar ]]; then .... Ce n'est toujours pas mieux que ce que Python a à offrir, mais c'est mieux que ce que vous avez présenté.
Shrikant Sharat

8
Certains systèmes (embarqués par exemple) n'ont pas de python disponible et vous ne pouvez / ne voulez pas installer des éléments supplémentaires.
Rui Marques

2
Personnellement, j'aime bash, mais je conviens que cela peut être un peu irritant. Vous devez généralement être beaucoup plus proactif alors qu'en Python, vous pouvez résoudre les erreurs une fois qu'elles se sont produites. Cependant, bash a trap(pour nettoyer / annuler en cas d'erreur) ainsi que regex (ie [[ $1 =~ ^[1-3]{3}$ ]]). Je suis à peu près sûr que la syntaxe obscure que vous avez utilisée fait référence à d'anciennes implémentations de test, pas à bash. Bash est idéal pour s'interfacer avec les outils de ligne de commande existants ... Souvent, un seul tube vers awkou grepest beaucoup plus facile que l'alternative Python.
Six

1
BTW, le module d'analyseur auquel vous faisiez référence est probable optparseou son successeur argparse. Je n'ai jamais vu personne utiliser le getoptmodule et je ne l'ai pas utilisé personnellement. L' getoptutilité est excellente cependant. L'analyse des arguments à partir du shell n'est pas du tout un problème une fois que vous avez un bon modèle. À moins que vous n'essayiez d'implémenter des sous-commandes de style git ou quelque chose du genre, ce n'est pas beaucoup de problèmes.
Six

Python ne fonctionnera pas partout où bash peut atteindre. Je dis cela parce que nous avons testé bash par rapport à python, même logique de code, et avons demandé aux deux de faire quelque chose. Bash est entré dans tous les répertoires auxquels il avait accès. D'un autre côté, python ne pouvait pas gérer certaines autorisations de répertoires et fichiers, ainsi que des répertoires qui augmentaient et diminuaient très rapidement.
vianna77
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.