Опыт разработки и тестирования RESTful
            JSON сервиса
Требования к системе
●   Удобство для реселлеров
●   Возможность брендирования
●   Гибкость и возможность расширения за
    счет подключаемых модулей
Реализация

                 Customer


F                 Login
           B
R
           A
O
     API   C
N                Domain
           K
T
           E
E
N
D
           N
           D      ...
                   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             +--   domain/
  +-- customer/             |     +-- lib/
  |   +-- lib/              |     |    +-- domain.pm
  |   |   +-- customer.pm   |     +-- t/
  |   +-- t/                +--   cpanel/
  +-- login/                |     +-- lib/
  |   +-- lib/              |     |    +-- cpanel.pm
  |   |   +-- login.pm      |     +-- t/
  |   +-- 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());
};
Controllers
Chimera/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 programs
use 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.t
1..78
ok 1 - Create: normal mailbox: HTTP code
ok 2 - Create: normal mailbox data is hashref as expected
ok 3 - Create: normal mailbox{success} data: scalar as expected
ok 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-suite
s-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

                         Сторонние API
Registrar
            cPanel API                   VPS API
  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

                            Сторонние API
   Registrar
               cPanel API                   VPS API
     API
Но...

                       Тесты
Процесс

                   API-сервер
                    API-сервер


   domains     cpanel                        vps

                            Сторонние API
   Registrar
               cPanel API                   VPS API
     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::Cover
package 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
Спасибо!

Илья Чесноков <chesnokov.ilya@gmail.com>
Вопросы?

Опыт разработки и тестирования RESTful JSON сервиса

  • 1.
    Опыт разработки итестирования RESTful JSON сервиса
  • 2.
    Требования к системе ● Удобство для реселлеров ● Возможность брендирования ● Гибкость и возможность расширения за счет подключаемых модулей
  • 3.
    Реализация Customer F Login B R A O API C N Domain K T E E N D N D ... VPS
  • 4.
    RESTful API ● Ресурсы (объекты) ● URI – идентификатор ресурса ● HTTP методы (POST, GET, PUT, PATCH, DELETE) ● Формат передачи данных на выбор (HTML, XML, JSON, …) ● Метаданные
  • 5.
    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 { … };
  • 6.
    Dancer dancer/ ... +-- config.yml +-- dancer.pl +-- domain/ +-- customer/ | +-- lib/ | +-- lib/ | | +-- domain.pm | | +-- customer.pm | +-- t/ | +-- t/ +-- cpanel/ +-- login/ | +-- lib/ | +-- lib/ | | +-- cpanel.pm | | +-- login.pm | +-- t/ | +-- t/ +-- vps/ +-- lib/ ... | +-- vps.pm +-- t/
  • 7.
    Routes get '/domain' =>sub : Auth( Admin Marketing Reseller ){ Chimera::API::Domain->get(params()); };
  • 8.
    Routes - Authentication usebase qw(Dancer::Chimera::App); use Dancer qw(:syntax); get '/domain' => sub : Auth( Admin Marketing Reseller ){ Chimera::API::Domain->get(params()); }; Dancer::Plugin::Auth::Extensible
  • 9.
    Routes - Controllers get'/domain' => sub : Auth( Admin Marketing Reseller ){ Chimera::API::Domain->get(params()); };
  • 10.
    Controllers Chimera/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
  • 11.
    Server-side programs use Dancer::Chimeraqw(catch_output); my %domain_data = catch_output( 'Chimera::API::Domain', 'create', %param ); return $domain_data{output} if $domain_data{error}; # Process output …;
  • 12.
    Тестирование <sam> Doing thetests afterwards is like putting a condom on after you've come.
  • 13.
    Test-driven development ● Добавить тест ● Запустить тесты: убедиться, что новые тесты не проходят ● Написать код ● Запустить тесты: убедиться, что все тесты проходят ● Рефакторинг ● Повторить
  • 14.
    Тестирование 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 }, }, }, );
  • 15.
    Тестирование API t/ (master)$ prove -v api.t 1..78 ok 1 - Create: normal mailbox: HTTP code ok 2 - Create: normal mailbox data is hashref as expected ok 3 - Create: normal mailbox{success} data: scalar as expected ok 4 - Create: normal mailbox{success} data: is '1' ok 5 - Create: normal mailbox data all matches ...
  • 16.
    Тестирование 1.Написать тест икод 2.Запустить тесты 3.Увидеть кучу непонятных ошибок... 4.Понять, что сервер не перезапустился... 5.Исправить ошибки 6.Вернуться к пункту 2
  • 17.
    Тестирование 1.Написать тест икод 2.Запустить тесты 3.Увидеть кучу непонятных ошибок... 4.Понять, что сервер не перезапустился... 5.Исправить ошибки 6.Вернуться к пункту 2
  • 18.
    Dancer::Test ● Test::ChimeraAPI::request() – request_remote() - if $ENV{CHIMERA_SERVER} ● LWP::UserAgent::request() – request_internal() ● Dancer::Test::dancer_response()
  • 19.
    Dancer::Test - плюсы ● Не нужно запускать сервер: – Держать открытую вкладку – Ждать рестарта – Получать ошибки соединения – Не отвлекаешься от процесса кодирования ● Загружаются только нужные приложения
  • 20.
    Dancer::Test - минусы ● Условия менее близки к реальным ● Загрузка приложений при каждом запуске теста → общее время тестирования выше
  • 21.
    Test::Class ● Фреймворк для представления тестов в виде классов ● Удобно для тестирования ОО-кода ● При работе с большим количеством тестов Curtis (Ovid) Poe http://www.modernperlbooks.com/mt/2009/03/organizing-test-suite s-with-testclass.html http://www.slideshare.net/Ovid/testing-with-testclass
  • 22.
    Test::Class - плюсы ● Однократная загрузка Dancer-приложений – Может пригодиться для любых “тяжёлых” модулей ● Много других “плюшек” – Фазы подготовки / очистки – Наследование – Тестирование отдельных классов через prove – Тестирование отдельных методов – и т.д.
  • 23.
    Test::Class - минусы ● Требует изучения (в том числе и исходников) ● Нужна реорганизация тестов
  • 24.
    Test::Class - тесты t/ +-- class.t +-- lib/ +-- Email/ +-- Test/ +-- Create.pm +-- Delete.pm +-- Get.pm +-- Patch.pm +-- Put.pm +-- Rename.pm +-- API.pm
  • 25.
    class.t #!/usr/bin/env perl use lib::abs'../../../lib'; use our::way; use Test::Class::Load lib::abs::path('lib');
  • 26.
    Удалённые системы Тесты API-сервер domains cpanel vps Сторонние API Registrar cPanel API VPS API API
  • 27.
    Удалённые системы ● Взаимодействие по сети – Медленно – Необходимо подключение к Интернет – Поддержка тестовых серверов ● Тормоза самих систем – Создание VPS занимает ~1 мин ● Конфликты при одновременном запуске тестов
  • 28.
    Mock-тестирование ● Пишем тесты и код ● Доводим до рабочего состояния с использованием реальной удалённой системы ● Записываем ответы сервера ● Подменяем записанными ответами реальные
  • 29.
    Mock-тестирование - протоколы ● HTTP (LWP::UserAgent) – большая часть систем ● Другие
  • 30.
    Test::LWP::Recorder ● Запись ответов сервера в файлы $request_md5sum ● Подстановка записанных ответов
  • 31.
    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)
  • 32.
    Test::LWP::UserAgent ● Inspired by Test::Mock::LWP::Dispatch by Yury Zavarin ● Активно (более-менее) разрабатывается ● Расширяет возможности LWP::UserAgent ● Содержит интересные и полезные фичи http://www.perladvent.org/2012/2012-12-12.html
  • 33.
    Test::LWP::UserAgent – register_psgi $useragent->register_psgi($hostname, $app); Используется любой PSGI-совместимый фреймворк. Например, Dancer :)
  • 34.
    Но... Тесты Процесс API-сервер domains cpanel vps Сторонние API Registrar cPanel API VPS API API
  • 35.
    Но... Тесты Процесс API-сервер API-сервер domains cpanel vps Сторонние API Registrar cPanel API VPS API API
  • 36.
    В Dancer всёглобально! ● Конфигурация (settings в Dancer::Config) – Serializer ● Хуки (singleton Dancer::Factory::Hook->hooks) – Аутентификация ● Переменные (vars в Dancer::SharedData) – Используются внутри нашего API, не определены в mocked API
  • 37.
    Инжекция mock-данных ● Test::ChimeraAPI::Mocking – Подготовка mocked классов в секции startup() – Глобальная переменная $Mock::Something::API ● Mock-данные инжектированы в сам тестовый класс ● Mock-класс проверяет наличие данных и возвращает их в порядке timestamp или отправляет запрос к серверу ● Запись в файл при необходимости
  • 38.
    Юнит-тестирование Юнит-тестирование – этотестирование каждого неделимого блока функциональности в изоляции – не только возвращаемых значений для различных аргументов, но также взаимодействия этих блоков с другими частями приложения путём имитации работы этих частей. http://tinyurl.com/c4rweaw
  • 39.
    Mocking в юнит-тестах packageProvisioner; 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; }
  • 40.
    Mocking в юнит-тестах subprovision_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(); }
  • 41.
    Devel::Cover ● Оценка покрытия тестами ● $^P ($PERLDB) == 0x104; – Выключена оптимизация ● Не работают атрибуты :(
  • 42.
    Devel::Cover package 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
  • 43.
    Документация проекта ● POD ● Pod::HTML5::Browser Код: https://github.com/LoonyPandora/Pod-HTML5-Browser Презентация: https://speakerdeck.com/loonypandora/documentation-for-fun-and-profit
  • 46.
  • 47.