Выпуск 24. Февраль 2015

От редактора. Опрос и Форум | Содержание | Реализация удаленного вызова процедур (RPC) в Perl с помощью Thrift

Тестирование черного ящика

Рассмотрены особенности тестирования приложений в целом как черного ящика

Продолжаем цикл статей про тестирование: Тестирование в Perl. Лучшие практики, Тестирование в Perl. Практика, Тестирование с помощью Mock-объектов.

Юнит-тестирование не дает ответа на вопрос «Работает ли система целиком?», так как каждый тест пишется как можно независимее от других подсистем, широко используются Mock-объекты и подстановки. Тестирование производится тем, кто реализует требуемый функционал и подразумевает, что тесты некоторым образом все-таки завязаны на реализацию.

Как же проверить, что система работает? Обычно система запускается в тестовом режиме, где некоторые подсистемы представляют собой тестовые реализации, например, подключение к банку, обращение к другому сайту и прочее. Далее на запущенную систему запускаются тесты, которые никоим образом не знают, как внутренне устроена система и тестируют только то, что доступно извне.

Для примера рассмотрим, как тестируется подписка на наш журнал. Подписка это важная подсистема, и тестировать ее вручную довольно утомительно, так как там несколько шагов, которые еще и разветвляются. Подписка должна обязательно работать, поэтому написание автоматических тестов и их выполнение перед каждым запуском в работу просто необходимы.

Настройка окружения

При запуске приложения указывается, что оно исполняется в тестовом режиме. В примере с журналом читается тестовый конфигурационный файл, где указаны путь к тестовой базе данных, настройки тестового почтового сервера и локальный URL.

С тестовой базой и локальным URL все понятно. Как же устроен тестовый почтовый сервер? Обычно используются sendmail или аналоги, в тестовом же режиме отправляемое сообщение просто пишется во временный файл, который создается и проверятся во время теста. Это позволяет проверить не только факт отправления сообщений, но и правильность их формата, текста и т.д.

Так, например, выглядит вспомогательная функция для чтения почты:

sub _read_email {
    my $self = shift;

    local $/;
    open my $fh, '<', '/tmp/mailer.log' or die $!;
    my $email = <$fh>;
    close $fh;

    my ($body) = $email =~ m/\r?\n\r?\n(.*)/ms;
    $body = MIME::Base64::decode_base64($body);
    return Encode::decode('UTF-8', $body);
}

Запуск приложения

Запускать приложение вручную не всегда удобно, это можно автоматизировать. Так как журнал совместим с PSGI-интерфейсом, приложение можно запускать прямо из теста. Тестирование осуществляется с помощью Test::WWW::Mechanize::PSGI, поэтому создать агента довольно просто:

sub _build_ua {
    my $self = shift;

    my $app = PP->new;
    return Test::WWW::Mechanize::PSGI->new(app => $app->to_app);
}

Таким образом с помощью созданного объекта можно выполнять различные HTTP-запросы и проверять их результаты.

Тестирование

Вначале убедимся, что при заходе на путь /newsletter пользователь получает правильную страницу:

subtest 'shows subscribe page' => sub {
    my $ua = $self->_build_ua;

    $ua->get_ok('/newsletter');
    $ua->content_contains('Подписка');
};

В данном случае мы проверям то, что нам нужно. Не стоит проверять, что есть навигационная панель, правильные пути к стилям или картинкам. Дизайн часто меняется и тесты будут ломаться, хотя сама функциональность будет сохранена. Чтобы этого избежать, тестируем только то, что необходимо для правильной работы системы.

Обычно при заполнении форм стоит проверить, что срабатывает валидация. Не стоит проверять каждое поле, это уже задача юнит-тестирования. Главное проверить, что все работает целиком. Например, проверим, что при незаполнении полей получаем ошибку:

subtest 'shows validation errors' => sub {
    my $ua = $self->_build_ua;

    $ua->get('/subscribe');
    $ua->submit_form(fields => {});
    $ua->content_contains(q{Обязательно к заполнению});
};

Далее проверяем сценарии подписки. Чтобы протестировать подписку, необходимо проверить, что пользователь получает правильное сообщение после нажатия кнопки и ему отправляется правильный email с правильной ссылкой на подтвержение подписки. Далее необходимо проверить, что при клике на ссылку подтверждения пользователь получает правильный email.

Здесь покажем как проверить, что email был отправлен:

subtest 'sends confirmation email' => sub {
    my $ua = $self->_build_ua;

    $ua->get('/subscribe');
    $ua->submit_form(fields => {email => 'foo@bar.com'});

    my $email = $self->_read_email;
    like($email, qr{subscribe/[a-z0-9]+});
    like($email, qr{unsubscribe/[a-z0-9]+});
};

Здесь используется вспомогательная функция для чтения отправленного email. Также проверяется наличие двух ссылок: одна для подтверждения, вторая для отписки, если письмо пришло по ошибке. Содержимое письма проверяется довольно общим образом, так как сообщения могут меняться. Главное, что есть правильные ссылки.

Для того, чтобы проверить отписывание от рассылки, необходимо каждый раз подписываться. Чтобы это автоматизировать, выделим отдельный метод для создания новой подписки:

sub _build_subscribed_ua {
    my $ua = $self->_build_ua;

    $ua->get('/subscribe');
    $ua->submit_form(fields => {email => 'foo@bar.com'});

    my $email = $self->_read_email;
    my ($token) = $email =~ m{subscribe/([a-z0-9]+)};

    $ua->get('/subscribe/' . $token);

    unlink '/tmp/mailer.log';

    return $ua;
}

Таким образом, мы получаем уже подписанный email и можем дальше проверять правильность удаления подписки без дублирования кода:

subtest 'sends confirmation email on unsubscribe' => sub {
    my $ua = $self->_build_subscribed_ua;

    $ua->get('/unsubscribe');
    $ua->submit_form(fields => {email => 'foo@bar.com'});

    my $email = $self->_read_email;
    like($email, qr{unsubscribe/[a-z0-9]+});
};

Заключение

Таким образом осуществляется тестирование системы целиком.

Здесь мы не проверяем, что email добавляется в базу данных, ведь она недоступна извне, это все проверяется в юнит-тестах на соответствующие контроллеры. Иногда функциональные тесты могут пересекаться с юнит-тестами, например, в юнит-тестах проверяется, что при неправильном вводе данных срабатывает валидация. Однако при работающей системе перед контроллером и после него могут быть различные подсистемы, и их слаженную работу как раз и стоит проверить.

Вячеслав Тихановский


От редактора. Опрос и Форум | Содержание | Реализация удаленного вызова процедур (RPC) в Perl с помощью Thrift
Нас уже 1393. Больше подписчиков — лучше выпуски!

Комментарии к статье