Upload
ilya-chesnokov
View
999
Download
5
Embed Size (px)
DESCRIPTION
Кое-что о процессах и технологиях, которые используются при разработке системы, основой которой является RESTful JSON API.
Citation preview
Опыт разработки и тестирования RESTful JSON сервиса
Требования к системе
● Удобство для реселлеров● Возможность брендирования● Гибкость и возможность расширения за
счет подключаемых модулей
Реализация
FRONTEND
BACKEND
API
Customer
Login
Domain
VPS
...
RESTful API
● Ресурсы (объекты)● URI – идентификатор ресурса● HTTP методы (POST, GET, PUT, PATCH,
DELETE) ● Формат передачи данных на выбор (HTML,
XML, JSON, …)● Метаданные
REST + Dancer
post '/email/:domain' => sub { … };
get '/email/:domain' => sub { … };
get '/email/:domain/:mailbox' => sub { … };
put '/email/:domain/:mailbox' => sub { … };
patch '/email/:domain/:mailbox' => sub { … };
del '/email/:domain/:mailbox' => sub { … };
Dancer
dancer/ +-- config.yml +-- dancer.pl +-- customer/ | +-- lib/ | | +-- customer.pm | +-- t/ +-- login/ | +-- lib/ | | +-- login.pm | +-- t/
...
...
+-- domain/ | +-- lib/ | | +-- domain.pm | +-- t/ +-- cpanel/ | +-- lib/ | | +-- cpanel.pm | +-- t/ +-- vps/ +-- lib/ | +-- vps.pm +-- t/
Routes
get '/domain' => sub : Auth(
Admin Marketing Reseller
) {
Chimera::API::Domain->get(params());
};
Routes - Authentication
use base qw(Dancer::Chimera::App);
use Dancer qw(:syntax);
get '/domain' => sub : Auth(
Admin Marketing Reseller
) {
Chimera::API::Domain->get(params());
};
Dancer::Plugin::Auth::Extensible
Routes - Controllers
get '/domain' => sub : Auth(
Admin Marketing Reseller
) {
Chimera::API::Domain->get(params());
};
ControllersChimera/API/ +-- Basket.pm +-- Basket/ | +-- Item.pm +-- Cpanel.pm +-- Cpanel/ | +-- Addondomain.pm | +-- SSL.pm +-- Customer.pm +-- Customer/ | +-- Service/ | +-- Service.pm | +-- User/ | +-- User.pm +-- Domain.pm ... +-- Utils.pm
Server-side programsuse Dancer::Chimera qw(catch_output);
my %domain_data = catch_output('Chimera::API::Domain','create',%param
);return $domain_data{output} if $domain_data{error};
# Process output…;
Тестирование
<sam> Doing the tests afterwards is like putting a condom on after you've come.
Test-driven development
● Добавить тест● Запустить тесты: убедиться, что новые тесты
не проходят● Написать код● Запустить тесты: убедиться, что все тесты
проходят● Рефакторинг● Повторить
Тестирование API
use Test::ChimeraAPI;
Test::ChimeraAPI::run_tests(
{
title => 'Create: normal mailbox',
call => [
POST => '/email' => \%mailbox_details,
],
expect => {
http_code => HTTP_CREATED,
data => { success => 1 },
},
},
);
Тестирование API
t/ (master) $ prove -v api.t1..78ok 1 - Create: normal mailbox: HTTP codeok 2 - Create: normal mailbox data is hashref as expectedok 3 - Create: normal mailbox{success} data: scalar as expectedok 4 - Create: normal mailbox{success} data: is '1'ok 5 - Create: normal mailbox data all matches...
Тестирование
1.Написать тест и код
2.Запустить тесты
3.Увидеть кучу непонятных ошибок...
4.Понять, что сервер не перезапустился...
5.Исправить ошибки
6.Вернуться к пункту 2
Тестирование
1.Написать тест и код
2.Запустить тесты
3.Увидеть кучу непонятных ошибок...
4.Понять, что сервер не перезапустился...
5.Исправить ошибки
6.Вернуться к пункту 2
Dancer::Test
● Test::ChimeraAPI::request()– request_remote() - if $ENV{CHIMERA_SERVER}
● LWP::UserAgent::request()
– request_internal()● Dancer::Test::dancer_response()
Dancer::Test - плюсы
● Не нужно запускать сервер:– Держать открытую вкладку
– Ждать рестарта
– Получать ошибки соединения
– Не отвлекаешься от процесса кодирования
● Загружаются только нужные приложения
Dancer::Test - минусы
● Условия менее близки к реальным● Загрузка приложений при каждом запуске
теста → общее время тестирования выше
Test::Class
● Фреймворк для представления тестов в виде классов
● Удобно для тестирования ОО-кода● При работе с большим количеством тестов
Curtis (Ovid) Poe
http://www.modernperlbooks.com/mt/2009/03/organizing-test-suites-with-testclass.html
http://www.slideshare.net/Ovid/testing-with-testclass
Test::Class - плюсы
● Однократная загрузка Dancer-приложений– Может пригодиться для любых “тяжёлых”
модулей
● Много других “плюшек”– Фазы подготовки / очистки
– Наследование
– Тестирование отдельных классов через prove
– Тестирование отдельных методов
– и т.д.
Test::Class - минусы
● Требует изучения (в том числе и исходников)
● Нужна реорганизация тестов
Test::Class - тесты
t/
+-- class.t
+-- lib/
+-- Email/
+-- Test/
+-- Create.pm
+-- Delete.pm
+-- Get.pm
+-- Patch.pm
+-- Put.pm
+-- Rename.pm
+-- API.pm
class.t
#!/usr/bin/env perl
use lib::abs '../../../lib';
use our::way;
use Test::Class::Load lib::abs::path('lib');
Удалённые системы
Тесты
API-сервер
domains cpanel vps
Сторонние APIRegistrar
API cPanel API VPS API
Удалённые системы
● Взаимодействие по сети– Медленно
– Необходимо подключение к Интернет
– Поддержка тестовых серверов
● Тормоза самих систем– Создание VPS занимает ~1 мин
● Конфликты при одновременном запуске тестов
Mock-тестирование
● Пишем тесты и код● Доводим до рабочего состояния с
использованием реальной удалённой системы
● Записываем ответы сервера● Подменяем записанными ответами
реальные
Mock-тестирование - протоколы
● HTTP (LWP::UserAgent) – большая часть систем
● Другие
Test::LWP::Recorder
● Запись ответов сервера в файлы $request_md5sum
● Подстановка записанных ответов
Test::LWP::Recorder
GET http://foreign.api/something/:id - $resp1
PATCH http://foreign.api/something/:id - $resp2
GET http://foreign.api/something/:id - $resp1 (а должен быть $resp3)
Test::LWP::UserAgent
● Inspired by Test::Mock::LWP::Dispatch by Yury Zavarin
● Активно (более-менее) разрабатывается● Расширяет возможности LWP::UserAgent● Содержит интересные и полезные фичи
http://www.perladvent.org/2012/2012-12-12.html
Test::LWP::UserAgent – register_psgi
$useragent->register_psgi($hostname, $app);
Используется любой PSGI-совместимый фреймворк.
Например, Dancer :)
Но...
Тесты
API-сервер
domains cpanel vps
Сторонние APIRegistrar
API cPanel API VPS API
Процесс
Но...
Тесты
API-сервер
Процесс
API-сервер
domains cpanel vps
Сторонние APIRegistrar
API cPanel API VPS API
В Dancer всё глобально!
● Конфигурация (settings в Dancer::Config)– Serializer
● Хуки (singleton Dancer::Factory::Hook->hooks)– Аутентификация
● Переменные (vars в Dancer::SharedData)– Используются внутри нашего API, не
определены в mocked API
Инжекция mock-данных
● Test::ChimeraAPI::Mocking– Подготовка mocked классов в секции startup()
– Глобальная переменная $Mock::Something::API
● Mock-данные инжектированы в сам тестовый класс
● Mock-класс проверяет наличие данных и возвращает их в порядке timestamp или отправляет запрос к серверу
● Запись в файл при необходимости
Юнит-тестирование
Юнит-тестирование – это тестирование каждого неделимого блока функциональности в изоляции – не только возвращаемых значений для различных аргументов, но также взаимодействия этих блоков с другими частями приложения путём имитации работы этих частей.
http://tinyurl.com/c4rweaw
Mocking в юнит-тестахpackage Provisioner;use Provisioner::Mapper;
my $provisioner_mapper;__PACKAGE__->reset_provisioner_mapper;
sub reset_provisioner_mapper {shift->provisioner_mapper('Provisioner::Mapper');
}sub provisioner_mapper {
if ($_[1]) { $provisioner_mapper = $_[1] };return $provisioner_mapper;
}
sub create {my ($self, $serviceplan) = @_;my $class = $self->provisioner_mapper($serviceplan->codename);return $class->new;
}
Mocking в юнит-тестахsub provision_regrade_check_sp_is_passed_to_provisioner :Tests { my $self = shift;
Provisioner->provisioner_mapper(Chimera::UnitTest::Mock::Generic->new([ { method => 'mapping', input => ['cpanel_shared_hosting'], output => 'Chimera::UnitTest::Mock::Provisioner::CheckRegradeMethodCall' }, ]));
my($order, $action) = $self->_place_order($self->basket()); my $processed = Chimera::API::Action->process($action);
Provisioner->reset_provisioner_mapper();}
Devel::Cover
● Оценка покрытия тестами● $^P ($PERLDB) == 0x104;
– Выключена оптимизация
● Не работают атрибуты :(
Devel::Coverpackage Dancer::Chimera::App;use attributes;
my %attrs;sub MODIFY_CODE_ATTRIBUTES { my ($package, $subref, @attrs) = @_; $attrs{ refaddr $subref } = \@attrs; return;}…my $subref = sub : Auth(MyRole) { … };…
http://tinyurl.com/bptkr9a - багрепорт в perl5.porters
Документация проекта
● POD● Pod::HTML5::Browser
Код:https://github.com/LoonyPandora/Pod-HTML5-Browser
Презентация:https://speakerdeck.com/loonypandora/documentation-for-fun-and-profit
Вопросы?