Testowanie jednostkowe skryptów bash

112

Mamy system, w którym oprócz kodu Java działa kilka skryptów bash. Ponieważ próbujemy przetestować wszystko, co mogłoby się zepsuć, a te skrypty bash mogą się zepsuć, chcemy je przetestować.

Problem w tym, że trudno jest testować skrypty basha.

Czy istnieje sposób lub najlepsza praktyka testowania skryptów bash? A może powinniśmy przestać używać skryptów bash i poszukać alternatywnych rozwiązań, które można przetestować?

nimcap
źródło
zobacz także: stackoverflow.com/questions/1315624/…
Chen Levy
możliwy duplikat testów jednostkowych dla skryptów powłoki
użytkownik
Przegląd istniejących narzędzi: medium.com/wemake-services/…
sobolevn

Odpowiedzi:

48

W rzeczywistości istnieje shunit2 , oparty na xUnit framework do testów jednostkowych dla skryptów powłoki opartych na Bourne. Sam tego nie używałem, ale może warto to sprawdzić.

Podobne pytania zadawano już wcześniej:

ire_and_curses
źródło
3
Mogę stwierdzić (gra słów zamierzona), że shunit2 (wersja 2.1.6) jest do tej pory nieco uszkodzony. AssertNull i assertNotNull nie działają, nawet jeśli podajesz im bezpośrednie wartości. assertEquals działa dobrze, ale myślę, że na razie będę musiał po prostu rzucić własne.
labirynt
@labyrinth, czy na pewno problem nie dotyczy tego: github.com/kward/shunit2/issues/53 "Jak poprawnie używać assertNull?"?
Victor Sergienko
1
@Victor Zdecydowanie możliwe, że nie byłem wystarczająco ostrożny z moimi podwójnymi cudzysłowami. Wkrótce wracam do roli, w której shunit2 lub jakiś system testowania jednostek bash będzie bardzo przydatny. Spróbuję ponownie.
labirynt
5
Jestem użytkownikiem i czasami współtworzę shunit2 i mogę potwierdzić, że projekt żyje i ma się dobrze w 2019 roku.
Alex Harvey
31

Otrzymałem następującą odpowiedź z grupy dyskusyjnej:

możliwe jest zaimportowanie (dołączenie, cokolwiek) procedury (funkcji, jakkolwiek się nazywa) z zewnętrznego pliku. To jest klucz do napisania skryptu testowego: dzielisz swój skrypt na niezależne procedury, które można następnie zaimportować zarówno do uruchomionego skryptu, jak i do skryptu testowego, a następnie masz tak prosty skrypt, jak to tylko możliwe.

Ta metoda przypomina wstrzykiwanie zależności dla skryptów i wydaje się rozsądna. Preferowane jest unikanie skryptów bash i używanie bardziej testowalnego i mniej niejasnego języka.

nimcap
źródło
4
Nie jestem pewien, czy powinienem głosować w górę czy w dół, z jednej strony dzielenie na mniejsze części jest dobre, ale z drugiej strony potrzebuję frameworka, a nie zestawu niestandardowych skryptów
mpapis
10
Chociaż nie ma nic złego w bashu (napisałem wiele, wiele skryptów), jest to trudny język do opanowania. Moja praktyczna zasada jest taka, że ​​jeśli skrypt jest wystarczająco duży, aby wymagać testów, prawdopodobnie powinieneś przejść do języka skryptowego, który jest łatwy do przetestowania.
Doug
1
Ale czasami trzeba mieć coś, co można pozyskać w powłoce użytkownika. Nie jest dla mnie jasne, jak byś to zrobił bez uciekania się do skryptu powłoki
Itkovian,
@Itkovian - możesz na przykład użyć npm do wyeksportowania pliku wykonywalnego na ścieżkę, więc nie jest konieczne sourcing (twój pakiet npm będzie musiał być zainstalowany globalnie)
Eliran Malka
1
Mam zamiar postępować zgodnie z radą, aby nie używać basha. :)
Maciej Wawrzyńczuk
30

Testowanie Bash zgodne z TAP : automatyczny system testowania Bash

TAP, protokół Test Anything Protocol, to prosty interfejs tekstowy między modułami testującymi w wiązce testowej. TAP rozpoczął życie jako część zestawu testowego dla Perla, ale teraz ma implementacje w C, C ++, Python, PHP, Perl, Java, JavaScript i innych.

Janus Troelsen
źródło
14
Warto ujawnić, czym jest TAP i dlaczego powinno nas to obchodzić, w przeciwnym razie to tylko bezsensowne kopiowanie i wklejanie
om-nom-nom.
@ om-nom-nom: Połączyłem to teraz z witryną TAP.
Janus Troelsen
7
Ponieważ nikt inny nie mówił czegoś niewypowiedzianego: TAP = Test Anything Protocol
JW.
9

Nikita Sobolev napisał doskonały wpis na blogu, w którym porównuje kilka różnych struktur testowych basha: Testowanie aplikacji Bash

Dla niecierpliwych: konkluzją Nikity było użycie Bats, ale wydaje się, że Nikita przegapił projekt Bats-core , który wydaje mi się być tym, który powinien być używany w przyszłości, ponieważ oryginalny projekt Bats nie był aktywnie utrzymywany od 2013 roku.

cb2
źródło
7

Epoxy to framework testowy Bash, który zaprojektowałem głównie do testowania innego oprogramowania, ale używam go również do testowania modułów bash, w tym siebie i Carton .

Główne zalety to stosunkowo niski narzut kodowania, nieograniczone zagnieżdżanie asercji i elastyczny wybór asercji do weryfikacji.

Zrobiłem prezentację porównującą to z BeakerLib - frameworkiem używanym przez niektórych w Red Hat.

spbnick
źródło
6

Dlaczego mówisz, że testowanie skryptów bash jest „trudne”?

Co jest nie tak z opakowaniami testów, takimi jak:

 #!/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
 }
Jim Dennis
źródło
4
Po pierwsze, skrypty bash nie są zbyt czytelne. Po drugie, oczekiwania są skomplikowane, podobnie jak sprawdzanie, czy plik blokady jest tworzony z PID skryptu bash, który go utworzył.
nimcap
10
Co ważniejsze, trudno jest testować skrypty powłoki, ponieważ generalnie mają one dużą liczbę skutków ubocznych i wykorzystują zasoby systemowe, takie jak system plików, sieć itp. W idealnym przypadku testy jednostkowe są wolne od skutków ubocznych i nie zależą od zasobów systemowych.
jayhendren
4

Stworzyłem shellspec ponieważ chciałem łatwego w użyciu i użytecznego narzędzia.

Został napisany przez czysty skrypt powłoki POSIX. Testował z wieloma pociskami bardziej niż shunit2. Ma potężne funkcje niż nietoperze / rdzeń nietoperzy.

Na przykład obsługa zagnieżdżonych bloków, łatwe do makiety / odgałęzienia, łatwe do pominięcia / oczekujące, testy sparametryzowane, numer linii asercji, wykonanie według numeru linii, wykonywanie równoległe, wykonywanie losowe, formatowanie TAP / JUnit, integracja pokrycia i CI, profiler itp. .

Zobacz demo na stronie projektu.

Koichi Nakashima
źródło
3

Bardzo podoba mi się shell2junit , narzędzie do generowania danych wyjściowych w stylu JUnit z testów skryptów Bash. Jest to przydatne, ponieważ wygenerowany raport może być następnie odczytany przez systemy ciągłej integracji, takie jak wtyczki JUnit dla Jenkins i Bamboo.

Chociaż shell2junit nie zapewnia kompleksowego środowiska skryptowego Bash, takiego jak shunit2 , pozwala na ładne raportowanie wyników testów.

Steve HHH
źródło
3

Wypróbuj bashtest . To prosty sposób na przetestowanie skryptów. Na przykład możesz do-some-work.shzmienić niektóre pliki konfiguracyjne. Na przykład dodaj nową linię PASSWORD = 'XXXXX'do pliku konfiguracyjnego/etc/my.cfg .

Piszesz polecenia basha wiersz po wierszu, a następnie sprawdzasz wyjście.

Zainstalować:

pip3 install bashtest

Tworzenie testów to po prostu pisanie poleceń basha.

Plik 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

Uruchom testy:

bashtest *.bashtest

Możesz znaleźć kilka przykładów tutaj i tutaj

pahaz
źródło
3

Może można to wykorzystać lub przyczynić się do tego

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

Przeznaczony do zapisywania wyników w protokole TAP, który moim zdaniem jest dobry dla CI i dobry dla tych, którzy chcą środowisk powłoki. Wyobrażam sobie, że niektóre rzeczy działają w środowisku powłoki, więc niektórzy mogą twierdzić, że należy je przetestować w środowisku powłoki.

hrob
źródło
3

Spróbuj assert.sh

source "./assert.sh"

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

Mam nadzieję, że to pomoże!

znak
źródło
3

Nie mogę uwierzyć, że nikt nie mówił o BHP ! Jest kompatybilny zarówno z TAP, jak i JUnit, jest czystą powłoką (to znaczy nie obsługuje innych języków), działa również samodzielnie, jest prosty i bezpośredni.

Testowanie wygląda następująco (fragmenty pobrane ze strony projektu):

#!/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

Prosty bieg:

$ 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

Ostatni test pokazuje, że nie jest w porządku, ale kod zakończenia ma wartość 0, ponieważ jest to plik TODO. Można również ustawić gadatliwość:

$ 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

Zmień jego nazwę, aby używać .trozszerzenia i umieść go w tpodkatalogu, a możesz użyć prove(1)(części Perla), aby go uruchomić:

$ 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

Ustaw OSHT_JUNITlub przeprowadź, -jaby wygenerować wyjście JUnit. JUnit można również łączyć z prove(1).

Użyłem tej biblioteki zarówno do testowania funkcji, pozyskując ich pliki, a następnie uruchamiając asercje z IS/ OKi ich negacjami, a także skrypty za pomocą RUN/ NRUN. Dla mnie ta struktura zapewnia największy zysk przy najmniejszym nakładzie pracy.

Daniel C. Sobral
źródło
1

Wypróbowałem wiele przedstawionych tutaj rozwiązań, ale większość z nich okazała się nieporęczna i trudna w użyciu, więc zbudowałem własny mały framework testowy: https://github.com/meonlol/t-bash

To tylko jeden plik w repozytorium, który możesz po prostu uruchomić bezpośrednio, z podstawowym zestawem potwierdzeń w stylu JUnit.

Użyłem go profesjonalnie w kilku wewnętrznych projektach i udało mi się sprawić, że nasze skrypty bash są bardzo stabilne i odporne na regresję.

leondepeon
źródło
0

Spójrz na Outthentic , jest prosty, rozszerzalny o wiele języków (Perl, Python, Ruby, Bash na wybór) i wieloplatformowy (Linux, Windows) framework do testowania dowolnych aplikacji wiersza poleceń.

Alexey Melezhik
źródło
-2

Trudno mi było usprawiedliwić używanie basha dla większych skryptów, gdy Python ma tak ogromne zalety:

  • Try / Except umożliwia pisanie bardziej niezawodnych skryptów z możliwością cofnięcia zmian w przypadku błędu.
  • Nie musisz używać niejasnej składni, takiej jak „if [ x"$foo" = x"$bar"]; then ... ”, która jest podatna na błędy.
  • Łatwe analizowanie opcji i argumentów przy użyciu getopt modułu (i jest jeszcze łatwiejszy moduł do analizowania argumentów, ale nazwa mi umyka).
  • Python umożliwia pracę z listami / dyktami i obiektami zamiast podstawowych ciągów znaków i tablic.
  • Dostęp do odpowiednich narzędzi językowych, takich jak regex, bazy danych (na pewno można wszystko potokować do mysqlpolecenia w bash, ale nie jest to najładniejszy sposób pisania kodu).
  • Nie trzeba się martwić o korzystaniu z poprawną formę $*lub "$*"lub "$@"lub $1lub "$1"spacje w nazwach plików nie jest problemem, etc, etc, etc.

Teraz używam basha tylko dla najprostszych skryptów.

za dużo php
źródło
3
Nie zaprzeczając, że Python ma zalety, ale druga uwaga nie jest zbyt dobrze sformułowana. To samo można było zrobić, co if [[ $foo = $bar ]]; then .... To wciąż nie jest lepsze niż to, co ma do zaoferowania Python, ale jest lepsze niż to, co przedstawiłeś.
Shrikant Sharat,
8
Niektóre systemy (np. Osadzone) nie mają dostępnego Pythona i nie możesz / nie chcesz instalować dodatkowych rzeczy.
Rui Marques
2
Osobiście uwielbiam bash, ale zgadzam się, że może być trochę drażliwy. Zwykle musisz być znacznie bardziej proaktywny, podczas gdy w Pythonie możesz naprawiać błędy po ich pojawieniu się. Jednak bash ma trap(aby wyczyścić / cofnąć w przypadku błędu), jak również wyrażenie regularne (tj [[ $1 =~ ^[1-3]{3}$ ]].). Jestem prawie pewien, że niejasna składnia, której użyłeś, odnosi się do starych implementacji testbash, a nie do bash. Bash świetnie nadaje się do łączenia się z istniejącymi narzędziami wiersza poleceń ... Często pojedynczy potok do awklub grepjest znacznie łatwiejszy niż alternatywa dla Pythona.
Szósty
1
BTW, prawdopodobnie moduł parsera, do którego się odnosisz, optparselub jego następca argparse. Nigdy nie widziałem nikogo, kto używa tego getoptmodułu, ani nie używałem go osobiście. getoptNarzędzie to jest jednak wielka. Parsowanie argumentów z powłoki nie stanowi żadnego problemu, jeśli masz już ładny wzór. O ile nie próbujesz zaimplementować komend podrzędnych w stylu git lub czegoś takiego, nie jest to duży problem.
Szósty
Python nie będzie działał wszędzie tam, gdzie dociera bash. Mówię to, ponieważ przetestowaliśmy bash i python, tę samą logikę kodu i poprosiliśmy o zrobienie czegoś. Bash wszedł do każdego katalogu, do którego miał dostęp. Z drugiej strony Python nie mógł obsłużyć niektórych uprawnień do katalogów i plików, a także katalogów, które rosły i malały bardzo szybko.
vianna77