外部APIを含めたWebアプリのテストをしてみる

外部APIを用いたWebアプリのコントローラテストについて。


たとえば
数値の2乗を結果として返すAPIサーバ
があるとして、
そのAPIを利用した結果をHTMLとして出力するアプリケーション
を考えてみます。


しかし、このように外部APIサーバを用いるとテストが難しくなります。
もしかしたらアクセスコントロールされているかもしれませんし、
メンテナンスされているかもしれませんし、
オフラインだとそもそもテストできないですよね!


そこで外部APIサーバを振舞うplackサーバをローカルで立ち上げて
それをproveで利用できるといいかも。


というわけでそのような環境を作ってみます。



サンプルWebアプリ *1

まずは /api に アクセスすると 外部APIサーバにアクセスしてその結果を返すだけのコントローラを書いてみます。
$host はテストからコントロールできるように環境変数をみています。
lib/ApiSample/Web/C/Root.pm

package ApiSample::Web::C::Root;
use strict;
use warnings;
use URI;

sub index {
    my ($class, $c) = @_;
    $c->render("index.tt");
}

sub api {
    my ($class, $c) = @_;
    my $num = $c->req->param('num') || 0;
    my $host = $ENV{API_SERVER} || "111.111.111.111";
    my $uri = URI->new;;
    $uri->scheme("http");
    $uri->host($host);
    $uri->path_query("/?num=$num");

    my $ua = LWP::UserAgent->new;
    my $res = $ua->get($uri->as_string);
    unless ( $res->is_success ) {
        die $res->message;
    }
    $c->render("api.tt", {content => $res->content});
}

1;

テンプレート
tmpl/api.tt

<<<[% $content %]>>>

テスト用plackアプリの作成

numを2乗して返すだけのWebアプリ。
テストの要件を満たすだけの作りにしておけばよいでしょう。
t/script/api.psgi

use strict;
use warnings;
use Plack::Request;

my $app = sub {
    my $env = shift;
    my $req = Plack::Request->new($env);
    my $num = $req->param('num') || 0;
    my $body = $num ** 2;

    return [
      200,
      [ "Content-Type" => "text/plain"],
      [ $body ]
    ];
};

テストクラスの作成

こんなのを書いておく。
自分の環境だとTest::mysqldのテストパッケージと一緒にしてたり。
t/lib/ApiSample/Test.pm

package ApiSample::Test;

use Any::Moose;

# ...

__PACKAGE__->meta->make_immutable;
no Any::Moose;

use Proc::Guard;
use Plack::Util;

# ...

sub start_plackup {
    my $self = shift;
    my %args = @_;
    my $app  = $args{app};
    my $port = $args{port} || Test::TCP::empty_port();
    my $envd  = $args{envd} || 'deployment';
    my @proc = (
        plackup =>
            (map { ( "-I" => $_ ) } @INC),
             '-p' => $port,
             '-a' => $app,
             '-E' => $envd,
    );
    Proc::Guard::proc_guard(@proc);
}

1;

テストファイル

まず最初にテストAPI用のplackアプリを起動してサーバ情報を環境変数に保存します。
その後mechするとテスト用のplackサーバに接続されます。
t/mech.t

use strict;
use warnings;
use Plack::Test;
use Plack::Util;
use Test::More;
use Test::Requires 'Test::WWW::Mechanize::PSGI';

use lib "./t/lib";
use ApiSample::Test;
use Test::TCP qw/wait_port/;

# Web API 用のplackアプリ起動
my $t = ApiSample::Test->new();
my $port = '55551';
my $app_api = $t->start_plackup(
    app => 't/script/api.psgi',
    port => $port,
    envd => 'production',
);
# 立ち上がるまで待つ...
wait_port($port);
# Web API 用の環境変数
$ENV{API_SERVER} = "localhost:$port";

# test
my $app = Plack::Util::load_psgi 'ApiSample.psgi';
my $mech = Test::WWW::Mechanize::PSGI->new(app => $app);
{
    my $uri = "/api?num=5";
    $mech->get_ok($uri);
    $mech->head_ok($uri, ['Content-Type' => 'text/html']);
    $mech->content_like( qr/<<<25>>>/, '5の2乗が返ってくるはず');
}

done_testing;

あとはproveするだけでOK。
数行のpsgi書くだけでAPIを含めたテストがお手軽に出来るのでウハウハですね!
plackバンザイ!


ちなみにTest::mysqldのように常時起動するようなスクリプトを書いてもいいと思いますが、
気になるほど遅くないので自分はこのままつかってます


※参考
http://www.slideshare.net/lestrrat/why-dont-you-do-your-test-fukuoka-perl-workshop-18

*1:サンプルではAmon2を利用しています