Автоматизоване тестування плагінів Redmine

За минулий рік ми розробили чотири плагіна для зв'язки 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 # hide for 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, ініціалізацію його бази і вже потім запуск тестів.
Приклад налаштування
.travis.yml
language: ruby
rvm:
- 2.3.0

додатки:
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

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 plugin backlogs
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

database.yml.travis
test:
adapter: postgresql
encoding: unicode
pool: 5
database: travis_ci_test
user: postgres

Резюме
Коротенько по основним пунктам пройшлися. Про деталях і подробицях — продовжимо в коментарях. Більше прикладів для тестів — в исходниках redmine_2fa, redmine_telegram_common.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.