23.06.2016Тестирование сервера Node.js
Вводная
Уже не раз в этом блоге я ратовал за тестирование собственного кода. Поскольку писать мне интересно на разных языках, то и тестировать приходится по-разному. Каждый язык имеет свои паттерны, используя которые получается наиболее удобный код. Каждая библиотека или среда имеет в каком-то виде реализованные подходы к тестированию.
Для тестирования кода на ноде я использую жасмин. Для запуска задач — галп.
Сегодня хочу рассказать о двух простых приёмах для тестирования кода сервера, которые позволяют выделять и тестировать только то, что нужно.
Кто запускает сервер
Первое, что хотелось бы сделать, это запускать сервер отдельно для каждого теста. Желательно на другом порту. Может быть, даже параллельно с работающим сервером, на котором мы что-то пробуем руками. Для этого в ноде есть возможность определить, находимся ли мы в основном файле, или его загружает какой-то другой файл:
if (require.main === module) {
app.use('/', require(path.join(__dirname, 'routes', 'main'))());
var server = app.listen(5000, function () {
...
});
} else {
exports = module.exports = function (options) {
app.use('/', require(path.join(__dirname, 'routes', 'main'))(options));
return app;
};
}
Таким образом, уже в тестовом фреймворке мы будем запускать:
app = require('index.js');
server = app(...).listen(6000, function (err) {...});
перед каждым тестом, а после каждого теста:
server.close(function (err) {...});
Очень удобно!
Имитация библиотек
Второе, что бы хотелось сделать при тестировании сервера, — имитировать используемые им библиотеки, чтобы тестировать только код сервера, а не библиотеки. Связки тоже важны, но только тогда, когда мы этого сами хотим. И тут появляется интересный момент. Если мы хотим имитировать библиотеки при тестировании, нам нужно использовать определённый паттерн при программировании. Паттерн фабрик. То есть мы не просто тестируем свой код, но устраиваем архитектуру и стиль так, чтобы его удобнее было тестировать.
В случае с паттерном фабрики мы возвращаем в качестве модуля не объект, а функцию, которая создаёт объект в соответствии с нашими параметрами:
exports = module.exports = function (options) {
options = options || {};
var customLib = options.customLib || require(...);
/* GET main page. */
router.get('/', function (req, res, next) {
res.end('Real server text. CustomLib: ' + customLib.name);
});
return router;
};
Как видно в вызовах выше, мы передаём сквозным образом наши опции при тестировании, а при нормальном запуске, не передаём ничего.
Таким образом, получается, что при разработке через тестирование, или даже просто при использовании тестов важно не только писать тесты, но и писать код, который, во-первых, можно, а во-вторых, удобно тестировать.
Для самостоятельного изучения
30.04.2009Тестирование Paperclip с помощью Factory Girl
Введение
Регулярно в своей работе я встречаюсь с новыми инструментами и решаю разные задачи. Иногда новые решения кажутся незначительными и слишком простыми для того, чтобы посвящать им целый пост в блоге. Но в нашем блоггерском деле главное — это регулярность. :)
Задача
Протестировать вновь изученный инструмент Paperclip с помощью вновь изученного инструмента Factory Girl.
Решение
Допустим, прикрепленный файл называется photo. То есть в модели написано следующее:
class Something < ActiveRecord::Base
has_attached_file :photo
end
Кладем файл с фото, например, в папку с fixtures. Тогда файл test/factories.rb (или spec/factories.rb) будет содержать:
Factory.define :something do |smth|
smth.photo File.new("path_to_file_in_fixtures", "rb")
end
И всё! Теперь в тестах, когда нужно создать объект пишем:
Factory.create(:something)
# или
Factory.build(:something)
А в тестах контроллера пишем:
Factory.attributes_for(:something)
Выводы
Если вы хотите написать проект за короткое ограниченное время, то лучше всего использовать инструменты, которые вы уже опробовали и знаете.
Если вы хотите изучить новые инструменты, то лучше всего сделать небольшой проектик с их использованием.
Проекты в первом и втором случае значительно отличаются по объему. :)
Материалы для изучения
20.04.2009Тестирование OpenID с помощью Cucumber
Задача
Какое-то время назад решил приобщиться к bdd (behavior driven development). С помощью такого подхода очень удобно описывать функционал приложения с точки зрения финального пользователя. И, соответственно, писать код так, чтобы выполнялись сценарии.
Для этого подхода существует прекрасный инструмент Cucumber, сценарии которого пишутся непосредственно текстом. Их, соответственно, можно даже обсуждать в качестве технического задания.
В качестве хорошего примера рассмотрим сценарий комментирования в этом блоге. Комментарии здесь можно оставлять только с помощью идентификации по OpenID.
Ресурсы
Нам понадобится, собственно, Cucumber, который для более лёгкого старта лучше использовать вместе с RSpec (фреймворк для удобного написания тестов и test driven development) и WebRat (инструмент для интегральных тестов, который содержит удобные методы работы с клиентским интерфейсом приложения).
Для работы с OpenID со стороны конечного пользователя нам понадобится локальный тестовый сервер OpenID. Есть хороший вариант: ROTS.
Затребуем библиотеки в тестовом окружении. В файле config/environments/test.rb:
config.gem "rspec", :lib => false, :version => ">=1.2.4"
config.gem "rspec-rails", :lib => false, :version => ">=1.2.4"
config.gem "webrat", :lib => false, :version => ">=0.4.4"
config.gem "cucumber", :lib => false, :version => ">=0.3.0"
config.gem "roman-rots", :lib => "rots", :version => ">=0.2.1"
Установим всё это:
sudo apt-get install libxml2-dev libxslt-dev
gem sources -a http://gems.github.com
rake gems:install RAILS_ENV=test
Решение
Предполагается, что как написать аутентификацию через OpenID мы знаем. Наша задача сейчас только протестировать функционал. В любом случае, когда мы приступаем к данному сценарию, у нас уже должны быть описаны и запрограммированы статьи и хотя бы создана модель для комментариев.
Теперь чтобы начать использовать Cucumber в приложении нужно запустить генератор:
script/generate cucumber
Процесс разработки в bdd выглядит так: сначала пишем сценарий, потом пишем всё остальное. Но в данном посте я просто покажу результат.
OpenID сервер
После установки библиотеки ROTS появляется консольная комманда:
rots
При запуске тестов необходимо, чтобы сервер был запущен. Для работы нам хватит параметров по умолчанию. В данном сервере уже есть пользователь, параметры для которого мы и будем использовать в тестах.
Сценарий Сucumber
Сценарии называются features. Создаем файл features/create_comments.feature:
Feature: Create Comments
In order to give feedback
As a reader
I want to create comments
Scenario: Create Comment
Given I visit a page for the published post with 1 approved comment
When I fill in "OpenID" with "http://localhost:1123/john.doe?openid.success=true"
And I fill in "Имя" with "John Doe"
And I fill in "Текст" with "Nice post!"
And I press "Отправить"
Then I should verify my OpenID
And I should see "John Doe"
And I should see "Nice post!"
And the post should have 2 approved comments
Многие из шагов уже определены с помощью WebRat и RSpec. Доопределим то, чего не хватает.
Шаги для статей
Тут есть моё внутреннее соглашение (с самим собой :). Когда я использую определенный артикль the, то имею в виду статью, которую я запоминаю в течение каждого сценария. В файле features/step_definitions/post_steps.rb:
Given /^I visit a page for the (published|draft) post with (\d+) (approved )?comments?$/ do |pub, cmnts_count, appr|
@post = Post.create!(:title => "Cucumber test post",
:body => "This is test post for cucumber comments",
:published => (pub == "published"))
cmnts_count.to_i.times do |cnt|
comment = @post.comments.build(:name => "Test User", :body => "Test body #{cnt}")
comment.openid_url = "http://test.example"
comment.approved = true unless appr.nil?
comment.save!
end
visit post_url(@post)
end
Then /^the post should have (\d+) (approved )?comments?$/ do |count, appr|
if appr.nil?
@post.comments.count.should == count.to_i
else
@post.approved_comments.count.should == count.to_i
end
end
Шаги для комментариев
Этот шаг специально написан для работы с ROTS, который, не требуя от пользователя никакого интерактивного взаимодействия в процессе теста отвечает на запрос редиректом подтверждая или отвергая авторизацию. В файле features/step_definitions/comment_steps.rb:
Then /^I should verify my OpenID$/ do
response = Net::HTTP.get_response(URI.parse(headers['location']))
response.class.should == Net::HTTPSeeOther
visit response['location']
end
Заключение
Вот и всё. Чтобы запустить тест, нужно выполнить:
cucumber features
Далее, конечно же, следует написать сценарии для пустых полей, для отказа в авторизации, для неправильного адреса OpenID и прочее. Но это, как мне кажется, не составляет труда.
Так же известно, что Cucumber поддерживает и русский язык. То есть сценарии можно писать и на русском. Но я пока не пробовал.
Материалы для изучения
RailsCast про авторизацию с помощью OpenID RailsCast про использование Cucumber Документация по Cucumber