[Из песочницы] Тестирование Bash-приложений
Недавно передо мной встала задача протестировать приложение, написанное на Bash. Изначально я решил использовать unit-тесты на Python, однако, мне не захотелось добавлять лишние технологии в проект. И пришлось выбирать тестовый фреймворк, родным языком которого является многострадальный Bash.
Обзор существующих решений
Когда я обратился к Google с запросом: что уже есть на выбор, ответом мне было не так много вариантов. Здесь я рассмотрю некоторые из них.
На какие критерии я буду обращать внимание?
- Зависимости: если уж брать тестовый фреймворк на Bash, то хотелось бы, чтобы он не тянул за собой еще: Python, Lua и еще пару системных пакетов (а такие есть).
- Сложность установки: так как одно из задач было развертывание continuous-development и continuous-integration в Travis, мне было важно, чтобы установку можно было сделать за вменяемое время и число шагов. Идеальные варианты: пакетные менеджеры, приемлимые:
git clone
,wget
. - Документация и поддержка: приложение должно работать на разных unix-дисрибутивах, соответственно и тесты должны быть работать везде, с учетом количества разных платформ, оболочек, их сочетаний и скорости их обновления, без сообщества и опыта других пользователей оставаться не хотелось бы.
- Наличие fixtures в каком-либо виде и/или (хотя бы!)
setup()
иteardown()
функций. - Вменяемые синтаксис для написание новых тестов. В мире Bash — очень важное требование.
- Привычный для меня вывод о результатах выполнения тестов: сколько прошло, что и где упало, в какой строчке (желательно).
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.
Из плюсов можно дополнительно выделить:
- Простота синтаксиса и использования.
- Хорошая документация, примеры использования.
- Возможность делать условный или безусловный пропуск (skip) тестов.
- Возможность fail-fast или run-all.
- Есть возможность сделать вывод ошибок подробным (если использовать флаг
-v
), изначально он не говорит, какие тесты падают.
Есть несколько серьезных минусов:
- На момент написания статьи на github горела красная иконка «build failing», такое выглядит пугающе.
- Фреймворк позиционирует себя как легкий, в нем для меня не хватает методов
setup()
иteardown()
, чтобы можно было подготовить необходимые данные для каждого теста и удалить их по его завершению. - Нет возможности запустить все тестовые файлы из конкретной папки.
Вывод: хороший инструмент, который я бы рекомендовал использовать, если нужно написать пару несложных тестов для скрипта. Для более серьезных задач — не подходит.
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
Данный фреймворк имеет ряд уникальных возможностей в своем классе:
- Возможность создавать наборы тестов (suites) внутри кода, такая функция может быть полезна, есть есть тесты под конкретные платформы или оболочки. Тогда можно использовать свои пространства имен, вроде
zsh_
,debian_
и т.д. - Есть функции
setUp
иtearDown
, которые выполняются для каждого теста, а ещеoneTimeSetUp
иoneTimeTearDown
, которые выполняются в начале и в конце тестирования. - Богатый выбор разных
assert
, есть возможность выводить номера строк, где падает тест, используя конструкцию${_ASSERT_EQUALS_}
, но только в оболочках, где поддерживается нумерация строк. Из документации:bash
(>=3.0),ksh
,pdksh
, иzsh
. - Есть возможность пропускать тесты.
Но есть и ряд существенных минусов, которые меня в итоге и оттолкнули:
- Нет какой-либо активности в проекте, все последние ошибки в Google.Code с 2012 года висят без решения, в репозиторий не было коммитов уже три года. В общем, беда.
- Не понятно, что и как ставить, последний релиз был в 2011 году. Связано с прошлым пунктом.
- Количество функций, даже слегка излишне, так существуют два способа проверить равенство:
assertEquals
иassertSame
. Мелочь, а удивляет. - Нет возможности запустить все файлы из папки.
Вывод: серьезный инструмент, который можно гибко настроить и превратить в незаменимую часть проекта, но пугает отсутствие внятной системы ведения проекта самого shunit2
. Я решил искать дальше.
roundup
Меня первоначально заинтересовал данный фреймворк, потому что он написан автором Sinatra
для Ruby
. А еще понравился синтаксис тестов, который напоминает привычный и знакомый Mocha
. По-умолчанию запускаются все функции, которые начинаются с it_
внутри файла. Что интересно, все тесты запускаются внутри собственного sandbox, что позволяет не допускать лишних ошибок. А вот как выглядят сами тесты, пример из документации:
describe "roundup(5)"
before() {
foo="bar"
}
after() {
rm -f foo.txt
}
it_runs_before() {
test "$foo" "=" "bar"
}
Примеров вывода нет, чтобы посмотреть — нужно поставить и проверить, плохо. Вот какие есть достоинства:
- Каждый тест запускается внутри своего sandbox, что очень удобно.
- Прост в использовании.
- Установка через
git clone
и./configure && make
, можно установить в локальную директорию с добавлением в$PATH
.
И минусов набралось достаточно:
- Нет возможности сделать
source
каких-то общих функций для всех тестов, но справедливости ради стоит сказать, что при помощи хака — можно. - Нет возможности запустить все тестовые файлы из папки.
- Документация пестрит
TODO
, а работы не ведутся уже пару лет. - Нельзя пропустить тест.
Вывод: абсолютно средняя такая штука, нельзя сказать, что плохая. Но и хорошей ее не назовешь. По функционалу схожа с 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
.