[Из песочницы] Тестирование Bash-приложений

Недавно передо мной встала задача протестировать приложение, написанное на Bash. Изначально я решил использовать unit-тесты на Python, однако, мне не захотелось добавлять лишние технологии в проект. И пришлось выбирать тестовый фреймворк, родным языком которого является многострадальный Bash.

Обзор существующих решений


Когда я обратился к Google с запросом: что уже есть на выбор, ответом мне было не так много вариантов. Здесь я рассмотрю некоторые из них.

На какие критерии я буду обращать внимание?

  1. Зависимости: если уж брать тестовый фреймворк на Bash, то хотелось бы, чтобы он не тянул за собой еще: Python, Lua и еще пару системных пакетов (а такие есть).
  2. Сложность установки: так как одно из задач было развертывание continuous-development и continuous-integration в Travis, мне было важно, чтобы установку можно было сделать за вменяемое время и число шагов. Идеальные варианты: пакетные менеджеры, приемлимые: git clone, wget.
  3. Документация и поддержка: приложение должно работать на разных unix-дисрибутивах, соответственно и тесты должны быть работать везде, с учетом количества разных платформ, оболочек, их сочетаний и скорости их обновления, без сообщества и опыта других пользователей оставаться не хотелось бы.
  4. Наличие fixtures в каком-либо виде и/или (хотя бы!) setup() и teardown() функций.
  5. Вменяемые синтаксис для написание новых тестов. В мире Bash — очень важное требование.
  6. Привычный для меня вывод о результатах выполнения тестов: сколько прошло, что и где упало, в какой строчке (желательно).


assert.sh


Один из первых вариантов, на который я обратил внимание, был небольшой фреймворк assert.sh. Достаточно хорошее решение: простое в установке, простое в использовании. Для того, чтобы написать первые тесты нужно создать файл tests.sh и в него написать всего-то (пример из документации):

Развернуть
. assert.sh

# `echo test` is expected to write "test" on stdout
assert "echo test" "test"
# `seq 3` is expected to print "1", "2" and "3" on different lines
assert "seq 3" "1\n2\n3"
# exit code of `true` is expected to be 0
assert_raises "true"
# exit code of `false` is expected to be 1
assert_raises "false" 1
# end of test suite
assert_end examples

Затем тесты можно запустить и посмотреть результаты:
$ ./tests.sh
all 4 examples tests passed in 0.014s.


Из плюсов можно дополнительно выделить:

  1. Простота синтаксиса и использования.
  2. Хорошая документация, примеры использования.
  3. Возможность делать условный или безусловный пропуск (skip) тестов.
  4. Возможность fail-fast или run-all.
  5. Есть возможность сделать вывод ошибок подробным (если использовать флаг -v), изначально он не говорит, какие тесты падают.


Есть несколько серьезных минусов:

  1. На момент написания статьи на github горела красная иконка «build failing», такое выглядит пугающе.
  2. Фреймворк позиционирует себя как легкий, в нем для меня не хватает методов setup() и teardown(), чтобы можно было подготовить необходимые данные для каждого теста и удалить их по его завершению.
  3. Нет возможности запустить все тестовые файлы из конкретной папки.


Вывод: хороший инструмент, который я бы рекомендовал использовать, если нужно написать пару несложных тестов для скрипта. Для более серьезных задач — не подходит.

shunit2


Дела с установкой shunit2 обстоят несколько хуже. Я не смог найти адекватного репозитория: есть некий проект на Google.Code, есть несколько проектов на github различной запущенности (3 года и 5 лет), есть даже несколько svn репозиториев. Соответственно, понять, какой релиз последний и откуда его качать — нереально. Но то мелочи. А как выглядят сами тесты? Вот несколько упрощенный пример из документации:

Развернуть
testAdding()
{
  result=`expr 1 + 2`
  assertEquals \
      "the result of '${result}' was wrong" \
      3 "${result}"
}

Выполнение:
$ /bin/bash math_test.sh
testAdding

Ran 1 test.

OK


Данный фреймворк имеет ряд уникальных возможностей в своем классе:

  1. Возможность создавать наборы тестов (suites) внутри кода, такая функция может быть полезна, есть есть тесты под конкретные платформы или оболочки. Тогда можно использовать свои пространства имен, вроде zsh_, debian_ и т.д.
  2. Есть функции setUp и tearDown, которые выполняются для каждого теста, а еще oneTimeSetUp и oneTimeTearDown, которые выполняются в начале и в конце тестирования.
  3. Богатый выбор разных assert, есть возможность выводить номера строк, где падает тест, используя конструкцию ${_ASSERT_EQUALS_}, но только в оболочках, где поддерживается нумерация строк. Из документации: bash (>=3.0), ksh, pdksh, и zsh.
  4. Есть возможность пропускать тесты.


Но есть и ряд существенных минусов, которые меня в итоге и оттолкнули:

  1. Нет какой-либо активности в проекте, все последние ошибки в Google.Code с 2012 года висят без решения, в репозиторий не было коммитов уже три года. В общем, беда.
  2. Не понятно, что и как ставить, последний релиз был в 2011 году. Связано с прошлым пунктом.
  3. Количество функций, даже слегка излишне, так существуют два способа проверить равенство: assertEquals и assertSame. Мелочь, а удивляет.
  4. Нет возможности запустить все файлы из папки.


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

roundup


Меня первоначально заинтересовал данный фреймворк, потому что он написан автором Sinatra для Ruby. А еще понравился синтаксис тестов, который напоминает привычный и знакомый Mocha. По-умолчанию запускаются все функции, которые начинаются с it_ внутри файла. Что интересно, все тесты запускаются внутри собственного sandbox, что позволяет не допускать лишних ошибок. А вот как выглядят сами тесты, пример из документации:

Развернуть
describe "roundup(5)"

before() {
    foo="bar"
}

after() {
    rm -f foo.txt
}

it_runs_before() {
    test "$foo" "=" "bar"
}



Примеров вывода нет, чтобы посмотреть — нужно поставить и проверить, плохо. Вот какие есть достоинства:

  1. Каждый тест запускается внутри своего sandbox, что очень удобно.
  2. Прост в использовании.
  3. Установка через git clone и ./configure && make, можно установить в локальную директорию с добавлением в $PATH.


И минусов набралось достаточно:

  1. Нет возможности сделать source каких-то общих функций для всех тестов, но справедливости ради стоит сказать, что при помощи хака — можно.
  2. Нет возможности запустить все тестовые файлы из папки.
  3. Документация пестрит TODO, а работы не ведутся уже пару лет.
  4. Нельзя пропустить тест.


Вывод: абсолютно средняя такая штука, нельзя сказать, что плохая. Но и хорошей ее не назовешь. По функционалу схожа с assert.sh, только чуть больше. Где использовать? Если хватает функционала assert.sh, но нужна функция before() или after().

bats


Скажу сразу, остановил свой выбор на данном фреймворке. Понравилось многое. Прежде всего — отличная документация: примеры использования, семантическая версификация, отдельно порадовал список проектов, которые используют bats.

bats использует следующий подход: тест считается пройденным, если все команды внутри него возвращают код 0 (как set -e). То есть каждая строка — проверка истинности. Вот как выглядят тесты, написанные на bats:

Развернуть
#!/usr/bin/env bats

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

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

И вывод:
$ bats addition.bats
 ✓ addition using bc
 ✓ addition using dc

2 tests, 0 failures


Вывод информации о тестах при помощи флага (--tap) можно представить в виде текста совместимого с Test Anything Protocol, для которого есть плагины для большего количества программ: Jenkins, Redmine и прочие.

В bats, помимо особенного синтаксиса для написания теста, есть много интересного:

  • Команда run позволяет запустить команду, а затем протестировать ее выходной код и текстовый вывод: для чего есть специальные переменные: $status и $output
  • Команда load позволяет загрузить для использования общую кодовую базу.
  • Команда skip позволяет пропустить тест при необходимости.
  • Функции setup() и teardown() позволяют настроить окружение и прибрать за собой.
  • Есть целый набор специальных переменных среды.
  • Есть возможность запустить все тестовые файлы внутри папки.
  • Активное сообщество.


Плюсов у bats объективно много, и я уже их перечислил, а вот минус я смог заметить только один:

  • bats отходит от валидного bash. Тесты необходимо писать в файлах с разрешением .bats, использовать другой shebang.


Вывод: качественный инструмент, практически без слабых мест. Советую к использованию.

P.S.


Если интересно посмотреть, что же получилось в итоге, то вот ссылка на тесты к моему free-time проекту git-secret.

© Habrahabr.ru