Pixel Pedals of Tomakomai

北海道苫小牧市出身の初老の日常

Haskellで書いたCGIをPerlでテストする

以下は、aとbに渡された数字を足し算するだけの簡単な(?)CGIです。

#!/usr/bin/env runghc
{- sum.cgi -}
import Network.CGI
import Control.Monad

cgiOutput (Just n) = do
  setHeader "Content-Type" "text/plain"
  output $ show n ++ "\n"
cgiOutput Nothing = do
  setHeader "Content-Type" "text/plain"
  output ""

cgiMain = do
  a <- getInput "a"
  b <- getInput "b"
  cgiOutput $ foldl (liftM2 (+)) (Just 0) (map (liftM read) [a, b])

main = runCGI $ handleErrors cgiMain

CGIを動かす

このCGIを動作させるためだけにApacheを用意するのはちと面倒です。perlからこのCGIを動かしたい場合には*1、マニュアルにあるように以下のようにします*2

plackup -MPlack::App::CGIBin -e 'Plack::App::CGIBin->new(root => "/path/to/cgi-bin")->to_app'

Plack::App::CGIBin

サーバが立ち上がったら、 http://localhost:5000/sum.cgi などにアクセスすればOKです。なおPlack::App::CGIBinはシェバンで動作が変わり、perlだとevalでそれ以外だとforkしてexecされます。

テストを書く

PerlにはCGI::Testというモジュールがあり、これを用いれば以下のようにテストを書くことができます。

use CGI::Test;

my $ct = CGI::Test->make(
    -base_url   => "http://a",
    -cgi_dir    => "t/cgi-bin",
);

my $page = $ct->GET("http://a/sum.cgi?a=1&b=3");

print "1..1\n";
ok 1, $page->raw_content eq "4\n";

ただ、 CGI::Test は最終リリースが2001年であり、Test::Moreとok関数の名前が被ってたり "defined(%hash) is deprecated" なwarningが出たり-base_urlが必須だったりと実用的に難しい状態です。

そこで、Plack::Testを使うとこのテストは以下のように書けます。

use Test::More;
use Plack::Test;
use HTTP::Request::Common;
use Plack::App::CGIBin;

test_psgi(
    app => Plack::App::CGIBin->new(root => "t/cgi-bin")->to_app,
    client => sub {
        my $cb = shift;
        my $res = $cb->(POST "/sum.cgi", [a => 1, b => 4]);
        is $res->content, "5\n";
    },
);

done_testing;

さらにTest::WWW::Mechanize::PSGIを使うと、ページ遷移を含めたテストも簡単に書けます*3

use Test::More;
use Test::WWW::Mechanize::PSGI;
use Plack::App::CGIBin;

my $mech = Test::WWW::Mechanize::PSGI->new(
    app => Plack::App::CGIBin->new(root => "t/cgi-bin")->to_app,
);
$mech->post_ok('/sum.cgi', {a => 7, b => 2});
$mech->content_is("9\n");

done_testing;

2010-11-17 追記

突っ込みもらったので、追記しときます。

CGI ってのは STDIN/STDOUT/Environment variables により IPC する excecutable なんだからさ、その特性をいかして、ちゃんと普通にテストできるでしょ

@tokuhirom

my $output = run_cgi(cgi => 'path/to/cgi', env => {...}, input => ...); とかで十分テストできる気がする。

@tokuhirom

まあ確かに、テストするためだけにわざわざPSGIを経由するってのは複雑になるだけで本質的ではないですね。たまたま今ある道具を組み合わせたら苦労せずにテストが書けちゃったってだけの話で。

@hiratara

2011-05-12 追記

LWP::Protocol::PSGIを使えば、WWW::MechanizeをそのままPSGIアプリケーションのテストに使えますね!

*1:わざわざperlの環境用意する方が面倒ですけどね!w

*2:半年前のlestrratさんのエントリも参照。

*3:tokuhiromさんの1年前のエントリも参照