За прошедший год мы разработали четыре плагина для связки Redmine + Telegram (раз, два, три, четыре).
Потихонечку вырабатываются свои Best Practices в отношении них. В этой заметке расскажем о тестировании и интеграции с Travis CI.
Почему решили о тестировании рассказать? Потому что тестирование плагина для Redmine — тот ещё квест.
Вначале...
Привычный rspec с первого захода не получилось прикрутить. Со второго и третьего тоже.
Примеров плагинов с хорошим покрытием тестами также не удалось найти. Большинство плагинов либо совсем без тестов, либо сами плагины такие простые, что тесты для них помещались в одном файле. Как правило это были тесты для вьюх. А нам нужно тестировать логику.
Лучшим примером оказались тесты самого Redmine. В этом проекте используются обычные rails default тесты без какого-либо маломальского оформления (пруф).
Отсутствие контекстов внутри одного теста затрудняет тестировать различные ситуации для одного и того же класса. Поэтому прямое следование такому подходу завело нас однажды в тупик.
Очень выручила книга Rails Test Prescription издания 2011 года. Из неё мы узнали про так как писать Rails default style тесты, а таже про гем shoulda, который и стал нашим спасением.
Примеры будем приводить из плагина redmine_2fa, который мы анонсировали в прошлом месяце. А также из новоиспечённого redmine_telegram_common, о котором тоже однажды расскажем.
Как запускать тесты?
Тесты запускаются и корня Redmine:
bundle exec rake redmine:plugins:test NAME=redmine_2fa
На версии Redmine 3.3.1 при выполнении этой команды сыпется много ruby warning-ов, чтобы сделать выдачу почище, нужно добавить следующую строку в test_helper.rb
:
$VERBOSE = nil # for hide ruby warnings
Для использования своих фикстур в тестах, в тот же файл нужно добавить
ActiveRecord::FixtureSet.create_fixtures(File.dirname(__FILE__) + '/fixtures/', [:auth_sources])
Где вместо :auth_sources
будут те фикстуры, которые необходимо подгрузить к тестам.
Gemfile
group :test do
gem 'shoulda'
end
shoulda — must have gem для тестирования плагинов. Остальные — в зависимости от потребностей для тестирования.
Тестирование патчей для контроллеров
class AccountControllerPatchTest < ActionController::TestCase
# В начале теста указываем какой контроллер тестируем
tests AccountController
# Загружаем фикстуры
fixtures :users, :email_addresses, :roles, :auth_sources
# Инициализируем общий для всех тестов контекст
setup do
@user = User.find(2) # jsmith
Redmine2FA.stubs(:active_protocols).returns(Redmine2FA::AVAILABLE_PROTOCOLS)
end
# Переходим к частным контекстам
context 'user without 2fa' do
context 'with valid login data' do
setup { post :login, username: 'jsmith', password: 'jsmith', back_url: 'http://test.host/' }
context 'prepare' do
should set_session[:otp_user_id].to(2)
should set_session[:otp_back_url].to('http://test.host/')
should 'set user instance variable' do
assert_equal @user, assigns(:user)
end
end
end
end
Stub-ы и mock-и
Нового искать не стали, используем гем mocha который используется в Redmine.
Примеры использования:
User.any_instance.stubs(:mobile_phone).returns('7894561230')
User.any_instance.expects(:authenticate_otp).returns(true)
Тестирование маршрутов
Тесты для маршрутов располагаются в папке integration/routing
.
В Redmine есть свои хелперы для тестирования:
should_route 'POST /redmine_2fa/bot/init' => 'otp_bot#create'
Но для сложных маршрутов, нужно использовать хелпер из рельс:
assert_routing({ method: 'post', path: '/redmine_2fa/bot/token:token/update' },
controller: 'otp_bot_webhook', action: 'update', token: 'token:token')
Unit-тесты
Их нет смысла подробно описывать обойдёмся небольшим примером:
context 'confirmation' do
setup do
User.any_instance.stubs(:mobile_phone).returns('79243216547')
end
should 'confirm mobile phone with valid code' do
User.any_instance.expects(:authenticate_otp).returns(true)
@user.confirm_mobile_phone('valid')
@user.reload
assert @user.mobile_phone_confirmed?
end
should 'return errors with invalid code' do
User.any_instance.expects(:authenticate_otp).returns(false)
@user.confirm_mobile_phone('invalid')
@user.reload
assert !@user.mobile_phone_confirmed?
assert @user.errors.present?
end
end
Теперь про CI
Как вы помните из начала статьи, тесты плагинов запускаются из корня Redmine. А код плагина самого Redmine не содержит.
Поэтому в процесс тестирования нужно включить установку Redmine, инициализацию его базы и уже потом запуск тестов.
Пример настройки
language: ruby
rvm:
- 2.3.0
addons:
postgresql: "9.4"
env:
- REDMINE_VER=3.3-stable
- REDMINE_VER=master
install: "echo skip bundle install"
before_script:
- psql -c 'create database travis_ci_test;' -U postgres
script:
- export TESTSPACE=`pwd`/testspace
- export NAME_OF_PLUGIN=redmine_telegram_common
- export PATH_TO_PLUGIN=`pwd`
- export PATH_TO_REDMINE=$TESTSPACE/redmine
- mkdir $TESTSPACE
- cp test/support/* $TESTSPACE/
- bash -x ./travis.sh
В install
стоит
install: "echo skip bundle install"
потому что основная логика установки запускается в скрипте
bash -x ./travis.sh
#/bin/bash
set -e
if [[ ! "$TESTSPACE" = /* ]] ||
[[ ! "$PATH_TO_REDMINE" = /* ]] ||
[[ ! "$REDMINE_VER" = * ]] ||
[[ ! "$NAME_OF_PLUGIN" = * ]] ||
[[ ! "$PATH_TO_PLUGIN" = /* ]];
then
echo "You should set"
" TESTSPACE, PATH_TO_REDMINE, REDMINE_VER"
" NAME_OF_PLUGIN, PATH_TO_PLUGIN"
" environment variables"
echo "You set:"
"$TESTSPACE"
"$PATH_TO_REDMINE"
"$REDMINE_VER"
"$NAME_OF_PLUGIN"
"$PATH_TO_PLUGIN"
exit 1;
fi
export RAILS_ENV=test
export REDMINE_GIT_REPO=git://github.com/redmine/redmine.git
export REDMINE_GIT_TAG=$REDMINE_VER
export BUNDLE_GEMFILE=$PATH_TO_REDMINE/Gemfile
# checkout redmine
git clone $REDMINE_GIT_REPO $PATH_TO_REDMINE
cd $PATH_TO_REDMINE
if [ ! "$REDMINE_GIT_TAG" = "master" ];
then
git checkout -b $REDMINE_GIT_TAG origin/$REDMINE_GIT_TAG
fi
mv $TESTSPACE/database.yml.travis config/database.yml
mv $TESTSPACE/additional_environment.rb config/
# create a link to the backlogs plugin
ln -sf $PATH_TO_PLUGIN plugins/$NAME_OF_PLUGIN
# install gems
bundle install
# run redmine database migrations
bundle exec rake db:migrate
# run plugin database migrations
bundle exec rake redmine:plugins:migrate
bundle exec rake db:structure:dump
# run tests
bundle exec rake redmine:plugins:test NAME=$NAME_OF_PLUGIN
Если будете пользоваться этим скриптом, то конфиг тестовой базы нужно разместить в файле test/support/database.yml.travis
test:
adapter: postgresql
encoding: unicode
pool: 5
database: travis_ci_test
user: postgres
Резюме
Коротенько по основным пунктам прошлись. О деталях и частностях — продолжим в комментариях. Больше примеров для тестов — в исходниках redmine_2fa и redmine_telegram_common.
Автор: Centos-admin.ru