- PVSM.RU - https://www.pvsm.ru -

Тестирование Bash-приложений

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

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

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

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

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

assert.sh [2]

Один из первых вариантов, на который я обратил внимание, был небольшой фреймворк 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" "1n2n3"
# 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. На момент написания статьи [3] на github горела красная иконка "build failing", такое выглядит пугающе.
  2. Фреймворк позиционирует себя как легкий, в нем для меня не хватает методов setup() и teardown(), чтобы можно было подготовить необходимые данные для каждого теста и удалить их по его завершению.
  3. Нет возможности запустить все тестовые файлы из конкретной папки.

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

shunit2 [4]

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

Развернуть

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 [6]

Меня первоначально заинтересовал данный фреймворк, потому что он написан автором Sinatra для Ruby. А еще понравился синтаксис тестов, который напоминает привычный и знакомый Mocha [7]. По-умолчанию запускаются все функции, которые начинаются с 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 [8]

Скажу сразу, остановил свой выбор на данном фреймворке. Понравилось многое. Прежде всего — отличная документация: примеры использования, семантическая версификация, отдельно порадовал список проектов, которые используют 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 [9], для которого есть плагины для большего количества программ: Jenkins, Redmine и прочие.

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

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

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

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

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

P.S.

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

Автор: sobolevn

Источник [13]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/bash/114546

Ссылки в тексте:

[1] Travis: https://travis-ci.com/

[2] assert.sh: https://github.com/lehmannro/assert.sh

[3] момент написания статьи: https://travis-ci.org/lehmannro/assert.sh/builds/74633036

[4] shunit2: https://github.com/kward/shunit2

[5] документации: https://shunit2.googlecode.com/svn/trunk/source/2.1/doc/shunit2.html#asserts

[6] roundup: https://github.com/bmizerany/roundup

[7] Mocha: https://mochajs.org/

[8] bats: https://github.com/sstephenson/bats

[9] Test Anything Protocol: http://testanything.org/

[10] целый набор специальных переменных среды: https://github.com/sstephenson/bats#special-variables

[11] ссылка на тесты: https://github.com/sobolevn/git-secret/tree/master/tests

[12] git-secret: https://sobolevn.github.io/git-secret/

[13] Источник: https://habrahabr.ru/post/278937/