93 votes

Test unitaire pour les scripts shell

Presque tous les produits sur lesquels j'ai travaillé au fil des ans ont nécessité un certain niveau de scripts shell (ou fichiers batch, PowerShell, etc. sur Windows). Même si nous avons écrit la majeure partie du code en Java ou en C++, il semblait toujours y avoir des tâches d'intégration ou d'installation mieux réalisées avec un script shell.

Les scripts shell font ainsi partie du code livré et doivent donc être testés tout comme le code compilé. Quelqu'un a-t-il une expérience avec certains des cadres de test unitaire de scripts shell qui existent, tels que shunit2 ? Je m'intéresse principalement aux scripts shell Linux pour le moment ; j'aimerais savoir dans quelle mesure le harnais de test reproduit la fonctionnalité et la facilité d'utilisation des autres cadres xUnit, et comment il est facile à intégrer avec des systèmes de build continus tels que CruiseControl ou Hudson.

0 votes

Roundup formalise certaines tâches / balises pour vous. Une fois que vous avez surmonté l'obstacle de l'apprentissage, c'est assez utile. Personnellement, j'aime mieux l'approche de haridsv, car cela ne nécessite pas que j'installe un autre paquet. J'ai appliqué cette approche aux tests de scripts shell et python.

1 votes

Vous pouvez également vérifier l'utilitaire bashtest : github.com/pahaz/bashtest (c'est une manière simple d'écrire des tests bash)

1 votes

Découvrez cet aperçu de presque tous les outils possibles : medium.com/wemake-services/…

60voto

Pete TerMaat Points 2135

MAJ 2019-03-01: Ma préférence est maintenant bats. Je l'ai utilisé pendant quelques années sur de petits projets. J'aime la syntaxe propre et concise. Je ne l'ai pas encore intégré à des frameworks CI/CD, mais son statut de sortie reflète le succès/échec global du lot, ce qui est mieux que shunit2 tel que décrit ci-dessous.


RÉPONSE PRÉCÉDENTE:

J'utilise shunit2 pour des scripts shell liés à une application web Java/Ruby dans un environnement Linux. Cela a été facile à utiliser et pas très éloigné des autres frameworks xUnit.

Je n'ai pas essayé de l'intégrer à CruiseControl ou Hudson/Jenkins, mais en mettant en œuvre l'intégration continue par d'autres moyens, j'ai rencontré ces problèmes :

  • Statut de sortie : Lorsqu'un lot de tests échoue, shunit2 n'utilise pas un statut de sortie différent de zéro pour communiquer l'échec. Vous devez donc soit analyser la sortie de shunit2 pour déterminer la réussite/l'échec d'un lot, soit modifier shunit2 pour se comporter comme certains frameworks d'intégration continue s'attendent, en communiquant la réussite/l'échec via le statut de sortie.
  • Logs XML : shunit2 ne produit pas de journal XML de style JUnit des résultats.

0 votes

Pete, je suis très proche de publier une bibliothèque de tests unitaires qui fonctionne pour bash et ksh, très simple à utiliser. Pouvez-vous m'envoyer un lien vers le fichier journal XML de style Junit quelque part pour que je puisse voir ce qu'il contient? Aussi, voudriez-vous obtenir le statut de sortie pour un seul test qui échoue ou à la fin lorsque tout est terminé, si > 0 tests échouent, sortie 1? La façon dont je le fais maintenant est qu'il y a une fonction que vous pouvez appeler pour obtenir le nombre de tests réussis/échoués.

0 votes

@EthanPost Je n'utilise plus / n'ai plus besoin de journaux XML. Vous pourriez trouver de l'aide en cherchant sur Google des choses comme "format XML xunit". Des choses comme ceci: xunit.github.io/docs/format-xml-v2.html

2 votes

Shunit maintenant se termine également avec le bon code de sortie: github.com/kward/shunit2/blob/…

25voto

haridsv Points 1394

Le récapitulatif par @blake-mizerany semble génial, et je devrais en faire usage à l'avenir, mais voici mon approche "pauvre" pour la création de tests unitaires :

  • Séparez tout ce qui est testable en tant que fonction.

  • Déplacez les fonctions dans un fichier externe, disons functions.sh et source le dans le script. Vous pouvez utiliser source `dirname $0`/functions.sh à cet effet.

  • À la fin de functions.sh, intégrez vos cas de test dans la condition suivante :

    if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    fi
  • Vos tests sont des appels littéraux aux fonctions suivis de simples vérifications des codes de sortie et des valeurs des variables. J'aime ajouter une fonction utilitaire simple comme celle ci-dessous pour faciliter l'écriture :

    function assertEquals()
    {
        msg=$1; shift
        expected=$1; shift
        actual=$1; shift
        if [ "$expected" != "$actual" ]; then
            echo "$msg ATTENDU=$expected ACTUEL=$actual"
            exit 2
        fi
    }
  • Enfin, exécutez functions.sh directement pour lancer les tests.

Voici un exemple pour montrer l'approche :

    #!/bin/bash
    function adder()
    {
        return $(($1+$2))
    }

    (
        [[ "${BASH_SOURCE[0]}" == "${0}" ]] || exit 0
        function assertEquals()
        {
            msg=$1; shift
            expected=$1; shift
            actual=$1; shift
            /bin/echo -n "$msg: "
            if [ "$expected" != "$actual" ]; then
                echo "ÉCHEC : ATTENDU=$expected ACTUEL=$actual"
            else
                echo RÉUSSI
            fi
        }

        adder 2 3
        assertEquals "ajout de deux nombres" 5 $?
    )

1 votes

Agréable, merci - J'apprécie beaucoup l'approche de programmation structurée pour les scripts shell.

0 votes

Très bonne observation là : 'Séparez tout testable en tant que fonction'. C'est important même si vous ne rédigez pas encore de test pour cela. C'est un facteur majeur pour améliorer la lisibilité du code. Il y a quelques années, j'ai commencé à écrire mon script (ksh) selon ce principe. Écrire tout comme une fonction.

0 votes

@HenkLangeveld La balise exit 0 ne va-t-elle pas provoquer la sortie du "sourcee" (celui qui source cela) également ? Je pense que vous cherchez à utiliser return ici.

19voto

Blake Mizerany Points 111

Revue: http://bmizerany.github.com/roundup/

Il y a un lien vers un article dans le README expliquant en détail.

12voto

vilpan Points 164

En plus de roundup et shunit2, ma revue des outils de test unitaire de shell incluait également assert.sh et shelltestrunner.

Je suis en grande partie d'accord avec la critique de l'auteur de roundup à l'égard de shunit2 (quelque peu subjective), donc j'ai exclu shunit2 après avoir examiné la documentation et les exemples. Bien que cela m'ait semblé familier, ayant une certaine expérience avec jUnit.

À mon avis, shelltestrunner est l'outil le plus original parmi ceux que j'ai examinés car il utilise une syntaxe déclarative simple pour la définition des cas de test. Comme d'habitude, tout niveau d'abstraction offre une certaine commodité au détriment de la flexibilité. Malgré que la simplicité soit attrayante, j'ai trouvé l'outil trop limité pour le cas que j'avais, principalement en raison de l'absence d'une façon de définir des actions de configuration/lecture (par exemple, manipuler les fichiers d'entrée avant un test, supprimer les fichiers d'état après un test, etc.).

J'étais d'abord un peu confus que assert.sh ne permette d'assertir que la sortie ou le statut de sortie, alors que j'avais besoin des deux. J'ai passé suffisamment de temps à écrire quelques cas de test en utilisant roundup. Mais j'ai vite trouvé le mode set -e de roundup inconfortable, car un statut de sortie non nul est attendu dans certains cas comme moyen de communiquer le résultat en plus de stdout, ce qui fait échouer le cas de test en mode dit. L'un des exemples montre la solution :

status=$(set +e ; rup roundup-5 >/dev/null ; echo $?)

Mais que faire si j'ai besoin à la fois du statut de sortie non nul et de la sortie ? Je pourrais bien sûr faire un set +e avant l'invocation et un set -e après ou un set +e pour l'ensemble du cas de test. Mais cela va à l'encontre du principe de roundup "Tout est une assertion". Donc j'avais l'impression de commencer à travailler contre l'outil.

À ce moment-là, j'ai réalisé que le "défaut" de assert.sh de ne permettre d'asserter que le statut de sortie ou la sortie est en fait un faux problème, car je peux simplement passer test avec une expression composée comme ceci

output=$($tested_script_with_args)
status=$?
expected_output="l'attente"
assert_raises "test \"$output\" = \"$expected_output\" -a $status -eq 2"

Comme mes besoins étaient vraiment basiques (exécuter un ensemble de tests, afficher que tout s'est bien passé ou ce qui a échoué), j'ai apprécié la simplicité de assert.sh, donc c'est ce que j'ai choisi.

3voto

D. Scott Points 11

Après avoir cherché un cadre de test unitaire simple pour shell qui pourrait générer des résultats xml pour Jenkins et ne pas vraiment trouver quelque chose,

Je l'ai écrit.

C'est sur sourceforge - le nom du projet est jshu.

http://sourceforge.net/projects/jshu

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X