Bash и разработка через тестирование - PullRequest
40 голосов
/ 22 августа 2009

Когда я пишу более чем тривиальный скрипт на bash, я часто задаюсь вопросом, как сделать код тестируемым.

Как правило, сложно писать тесты для кода bash, поскольку в нем мало функций, которые принимают значение и возвращают значение, а в функциях, которые проверяют и задают какой-либо аспект в среде, изменяется файл. -системы, вызова программы и т. д. - функций, которые зависят от среды или имеют побочные эффекты. Таким образом, код установки и тестирования становится намного сложнее, чем код, который они тестируют.


Например, рассмотрим простую функцию для проверки:

function add_to_file() {
  local f=$1
  cat >> $f
  sort -u $f -o $f
}

Тестовый код для этой функции может состоять из:

add_to_file.before:

foo
bar
baz

add_to_file.after:

bar
baz
foo
qux

И тестовый код:

function test_add_to_file() {
   cp add_to_file.{before,tmp}
   add_to_file add_to_file.tmp
   cmp add_to_file.{tmp,after} && echo pass || echo fail
   rm add_to_file.tmp
}

Здесь 5 строк кода проверяются 6 строками кода теста и 7 строками данных.


Теперь рассмотрим немного более сложный случай:

function distribute() {
   local file=$1 ; shift
   local hosts=( "$@" )
   for host in "${hosts[@]}" ; do
     rsync -ae ssh $file $host:$file
   done
}

Я даже не могу сказать, как начать писать тест для этого ...


Итак, есть ли хороший способ сделать TDD в скриптах bash, или я должен сдаться и приложить свои усилия в другом месте?

Ответы [ 8 ]

34 голосов
/ 12 сентября 2009

Итак, вот что я узнал:

  1. Есть некоторые тестирование фреймворки написанные на bash и для bash, однако ...

  2. Это не так много, что Bash не подходит для TDD (хотя некоторые другие языки приходят на ум, которые лучше подходят), но типичные задачи, для которых используется Bash (Установка, Система конфигурации), которые трудно написать тесты, и в особенно сложно настроить тест.

  3. Плохая поддержка структуры данных в Bash затрудняет разделение логика от побочного эффекта, и действительно, как правило, мало логики в скриптах Bash. Это затрудняет разбиение сценариев на тестируемые куски. Есть некоторые функции, которые можно протестировать, но это исключение, а не правило.

  4. Функция - хорошая вещь (тм), но они могут зайти так далеко.

  5. Вложенные функции могут быть еще лучше, но они также ограничены.

  6. В конце дня, при больших усилиях, может быть полученный, но он будет тестировать менее интересную часть кода, и сохранит основную часть тестирования как хорошее (или плохое) старое руководство тестирование.

Мета: Я решил ответить (и принять) свой собственный вопрос, потому что я не мог выбрать между Синан Юнюр (проголосовал) и Мувициелем (проголосовал), которые отвечают где одинаково полезно и проницательно. Хочу отметить ответ Стефано Борини , что, хотя поначалу меня это не впечатлило, я научился ценить его с течением времени. Также были полезны его шаблоны проектирования или лучшие практики для сценариев оболочки, ответ на которые (проголосовал выше), упомянутый выше.

11 голосов
/ 22 августа 2009

Если вы пишете код одновременно с тестами, постарайтесь включить в него функции, которые ничего не используют, кроме своих параметров, и не изменяют среду. То есть, если ваша функция может работать в подоболочке, то ее будет легко протестировать. Он принимает некоторые аргументы и выводит что-то на стандартный вывод, или в файл, или, может быть, он что-то делает в системе, но вызывающая сторона не чувствует побочных эффектов.

Да, вы получите большую цепочку функций, передающую некоторую переменную WORKING_DIR, которая также может быть глобальной, но это незначительное неудобство по сравнению с задачей отслеживания того, что каждая функция читает и изменяет. Включение юнит-тестов тоже просто бесплатный бонус.

Постарайтесь свести к минимуму случаи, когда вам нужен вывод. Небольшое злоупотребление недолговечностью поможет сохранить хорошее разделение (за счет производительности).

Вместо линейной структуры, где вызываются функции, задайте некоторую среду, затем вызываются другие, все в значительной степени на одном уровне, попробуйте перейти к глубокому дереву вызовов с минимальным возвратом данных. Возвращать вещи в bash неудобно, если вы принимаете добровольное воздержание от глобальных перемен ...

8 голосов
/ 24 августа 2009

С точки зрения реализации, я предлагаю shUnit .

С практической точки зрения я предлагаю не сдаваться. Я использую TDD в скриптах bash и подтверждаю, что оно того стоит.

Конечно, я получаю примерно вдвое больше строк теста, чем кода, но со сложными сценариями усилия по тестированию - это хорошая инвестиция. Это особенно верно, когда ваш клиент передумает в конце проекта и изменит некоторые требования. Наличие набора регрессионных тестов - большая помощь в изменении сложного кода Bash.

5 голосов
/ 22 августа 2009

Если вы пишете программу bash, достаточно большую, чтобы требовать TDD, вы используете неправильный язык.

Я предлагаю вам прочитать мой предыдущий пост о передовых практиках в программировании на bash, вы, вероятно, найдете что-то полезное, чтобы сделать вашу программу bash тестируемой, но мое утверждение выше остается.

Шаблоны проектирования или лучшие практики для сценариев оболочки

3 голосов
/ 23 июня 2013

Возможно, вы захотите взглянуть на огурец / Арубу. Проделал довольно хорошую работу для меня.

Кроме того, вы можете заглушить все, что хотите, выполнив что-то вроде этого:

#
# code.sh
#
some_function_calling_some_external_binary()
{
    if ! external_binary action_1; then
        # ...
    fi  

    if ! external_binary action_2; then
        # ...
    fi
}

#
# test.sh
#

# now for the test, simply stub your external binary:
external_binary()
{
    if [ "$@" = "action_1" ]; then
        # stub action_1

    elif [ "$@" = "action_2" ]; then
        # stub action_2

    else
        external_binary $@
    fi  
}
3 голосов
/ 09 октября 2011

Написание того, что Месарош называет потребительские тесты , трудно на любом языке. Другой подход заключается в проверке поведения команд, таких как rsync, вручную, а затем в написании модульных тестов для проверки конкретной функциональности, не затрагивая сеть. В этом слегка измененном примере $ run используется для печати побочных эффектов, если скрипт запускается с ключевым словом «test»

function distribute {
    local file=$1 ; shift
    for host in $@ ; do
        $run rsync -ae ssh $file $host:$file
    done
}

if [[ $1 == "test" ]]; then
    run="echo"
else
    distribute schedule.txt $*
    exit 0
fi

#    
# Built-in self-tests
#

output=$(mktemp)
expected=$(mktemp)
set -e
trap "rm $got $expected" EXIT

distribute schedule.txt login1 login2 > $output
cat << EOF > $expected
rsync -ae ssh schedule.txt login1:schedule.txt
rsync -ae ssh schedule.txt login2:schedule.txt
EOF
diff $output $expected
echo -n '.'

echo; echo "PASS"
0 голосов
/ 07 октября 2017

взгляните на Outthentic framework - он предназначен для создания сценариев, которые запускают любой код Bash, а затем анализирует стандартный вывод с использованием формального DSL, довольно просто построить любой набор тестов Tdd / blackbox на этом инструменте ,

0 голосов
/ 06 октября 2017

В расширенном руководстве по сценариям bash есть пример функции assert, но здесь есть более простая и более гибкая функция assert - просто используйте eval из $ * для проверки любого условия.

assert() {
  if ! eval $* ; then
      echo
      echo "===== Assertion failed:  \"$*\" ====="
      echo "File \"$0\", line:$LINENO line:${BASH_LINENO[*]}"
      echo line:$(caller 0)
      exit 99
  fi  
}

# e.g. USAGE:
assert [[ $r == 42 ]]
assert "((r==42))"

BASH_LINENO и встроенная функция вызывающей стороны bash зависят от оболочки bash.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...