web-dev-qa-db-fra.com

Tests unitaires pour les scripts Shell

Presque tous les produits sur lesquels j'ai travaillé au fil des ans ont impliqué 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 C++, il semblait toujours y avoir des tâches d'intégration ou d'installation qui étaient mieux effectuées avec un script Shell.

Les scripts Shell font donc partie du code livré et doivent donc être testés tout comme le code compilé. Quelqu'un a-t-il de l'expérience avec certains des cadres de test d'unité de script Shell qui existent, tels que shunit2 ? Pour l'instant, je m'intéresse principalement aux scripts Linux Shell; Je voudrais savoir dans quelle mesure le faisceau de test reproduit la fonctionnalité et la facilité d'utilisation d'autres frameworks xUnit, et comment il est facile de s'intégrer à des systèmes de construction continue tels que CruiseControl ou Hudson.

66
gareth_bowles

MISE À JOUR 2019-03-01: Ma préférence est chauves-souris maintenant. Je l'utilise depuis quelques années sur de petits projets. J'aime la syntaxe claire et concise. Je ne l'ai pas intégré aux frameworks CI/CD, mais son état de sortie reflète le succès/échec global de la suite, qui est meilleur que shunit2 comme décrit ci-dessous.


RÉPONSE PRÉCÉDENTE:

J'utilise shunit2 pour les scripts Shell liés à une application Web Java/Ruby dans un environnement Linux. Il a été facile à utiliser et ne se démarque pas des autres frameworks xUnit.

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

  • État de sortie: lorsqu'une suite de tests échoue, shunit2 n'utilise pas un état de sortie différent de zéro pour communiquer l'échec. Vous devez donc soit analyser la sortie shunit2 pour déterminer la réussite/l'échec d'une suite, soit modifier shunit2 pour se comporter comme le prévoient certains cadres d'intégration continue, en communiquant la réussite/l'échec via l'état de sortie.
  • Journaux XML: shunit2 ne produit pas de journal XML des résultats de style JUnit.
49
Pete TerMaat

Vous vous demandez pourquoi personne n'a mentionné CHAUVES-SOURIS . Il est à jour et APPUYEZ SUR - conforme.

Décris:

#!/usr/bin/env bats

@test "addition using bc" {
  result="$(echo 2+2 | bc)"
  [ "$result" -eq 4 ]
}

Courir:

$ bats addition.bats
 ✓ addition using bc

1 tests, 0 failures
27
phil pirozhkov

Roundup by @ blake-mizerany sonne bien, et je devrais l'utiliser à l'avenir, mais voici mon approche "pauvre-homme" pour créer des tests unitaires:

  • Séparez tout testable en fonction.
  • Déplacer des fonctions dans un fichier externe, par exemple functions.sh et source dans le script. Vous pouvez utiliser source `dirname $0`/functions.sh dans ce but.
  • Au bout du functions.sh, intégrez vos cas de test dans les conditions ci-dessous si:

    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 EXPECTED=$expected ACTUAL=$actual"
            exit 2
        fi
    }
    
  • Enfin, exécutez functions.sh directement pour exécuter 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 "FAILED: EXPECTED=$expected ACTUAL=$actual"
            else
                echo PASSED
            fi
        }

        adder 2 3
        assertEquals "adding two numbers" 5 $?
    )
20
haridsv

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

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

17
Blake Mizerany

En plus de roundup et shunit2 ma vue d'ensemble des outils de test d'unité Shell comprenait également assert.sh et shelltestrunner .

Je suis principalement d'accord avec la critique de shunit2 par l'auteur (en partie subjective), j'ai donc exclu shunit2 après avoir examiné la documentation et les exemples. Cependant, il semblait familier d'avoir une certaine expérience avec jUnit.

À mon avis, shelltestrunner est le plus original des outils que j'ai examinés car il utilise une syntaxe déclarative simple pour la définition de cas de test. Comme d'habitude, n'importe quel niveau d'abstraction donne une certaine commodité au prix d'une certaine flexibilité. Même si la simplicité est attrayante, j'ai trouvé l'outil trop limitatif pour le cas que j'avais, principalement en raison de l'absence de moyen de définir les actions setup/tearDown (par exemple, manipuler des fichiers d'entrée avant un test, supprimer des fichiers d'état après un test , etc.).

Au début, j'étais un peu confus car assert.sh ne permet d'affirmer que l'état de sortie ou de sortie, alors que j'avais besoin des deux. Suffisamment long pour écrire quelques cas de test en utilisant Roundup. Mais j'ai vite trouvé le roundup's set -e mode gênant car un état de sortie non nul est prévu dans certains cas comme moyen de communiquer le résultat en plus de stdout, ce qui fait échouer le cas de test dans ce mode. n des exemples montre la solution:

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

Mais que se passe-t-il si j'ai besoin de l'état de sortie non nul et de la sortie? Je pourrais, bien sûr, set +e avant l'invocation et set -e après ou set +e pour l'ensemble du scénario de test. Mais c'est contre le principe de la rafle "Tout est une assertion" . J'ai donc eu l'impression de commencer à travailler contre l'outil.

D'ici là, j'ai réalisé que "l'inconvénient" de assert.sh de permettre d'affirmer uniquement l'état de sortie ou la sortie n'est en fait pas un problème car je peux simplement passer test avec une expression composée comme celle-ci

output=$($tested_script_with_args)
status=$?
expected_output="the expectation"
assert_raises "test \"$output\" = \"$expected_output\" -a $status -eq 2"

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

8
vilpan

Après avoir recherché un cadre de test unitaire simple pour Shell qui pourrait générer des résultats xml pour Jenkins et ne trouver vraiment rien, j'en ai écrit un.

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

http://sourceforge.net/projects/jsh

2
D. Scott

Vous devriez essayer la assert.sh lib, très pratique, facile à utiliser

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

J'ai récemment publié un nouveau framework de test appelé shellspec.

shellspec est un framework de test de style BDD. Il fonctionne sur un script Shell compatible POSIX, y compris bash, dash, ksh, busybox, etc.

Bien sûr, l'état de sortie reflète le résultat de l'exécution des spécifications et il a un formateur compatible TAP.

Le fichier de spécifications est proche du langage naturel et facile à lire, ainsi que sa syntaxe compatible avec les scripts Shell.

#shellcheck Shell=sh

Describe 'sample'
  Describe 'calc()'
    calc() { echo "$(($*))"; }

    It 'calculates the formula'
      When call calc 1 + 2
      The output should equal 3
    End
  End
End
1
koichi nakashima