GoutteとPHPUnitでレガシーなPHPアプリケーションをテストする

GoutteでレガシーPHPをテストする
Goutte という PHP 製の Web スクレイピングライブラリを使って Web ブラウザを操作するテストを書いてみたのでサンプルを紹介します。
Goutte とは、Symfony 開発者の Fabian が手がけるライブラリです。Symfony コンポーネントの BrowserKit, CssSelector, DomCrawler ほか Guzzle HTTP というライブラリを使っていて、PHPUnit と組み合わせることで Symfony のファンクショナルテストと同じ感覚でブラウザテストを書くことができます。
レガシーな PHP アプリケーションのテストに悩んでいる方は選択肢のひとつとして参考にしてみてください。サンプルは karakaram/goutte-sample – GitHub に置いておきます。

動作確認環境

  • PHP 5.6
  • PHPUnit 4.7
  • Goutte 2.0


目次

  1. Goutte をインストール
  2. PHPUnit から使ってみる
  3. Fixture
  4. その他、テスト書くときの tips

Goutte をインストール

Composer からインストールします。


$ composer require fabpot/goutte

Goutte は PHP5.3 以降で動作しますが、古いバージョンの Goutte を使わなければいけないので PHP5.4 以降での利用がオススメです。この記事のサンプルも 5.4 以降で動作確認しています。
参考サイト

PHPUnit から使ってみる

Facebook にログインして友達を探すリンクをクリックし、友達リクエストのページを開くテストを書いてみました。email とパスワードをご自身のものに変更して試してみてください。


request('GET', 'https://ja-jp.facebook.com');
        $this->assertSame(
            'Facebook - フェイスブック - ログイン (日本語)',
            $crawler->filter('title')->first()->text()
        );
        //ログインボタンクリック
        $form = $crawler->selectButton('ログイン')->form();
        $crawler = $client->submit(
            $form,
            [
                'email' => 'hoge@example.com', //ログインメールアドレス
                'pass' => 'password'           //パスワード
            ]
        );
        //友達を検索リンクをクリック
        $crawler->filter('#findFriendsNav')->link();
        //友達を検索ページ表示
        $crawler->filter('h2')->each(
            function (Crawler $node) {
                $this->assertSame('知り合いかも', $node->text());
            }
        );
    }
}

ちなみに Symfony のファンクショナルテストはこんな感じ。すごく似てますね。
テスト | Symfony2日本語ドキュメント
DomCrawler コンポーネントの詳細な使い方はこちら
DomCrawlerコンポーネント | Symfony2日本語ドキュメント

Fixture

Web アプリケーションの結合テストを書くときに必要になってくるのが Fixuture です。今回は Doctrine DBAL と SQL で Fixture を作る例を紹介します。

Doctrine DBAL をインストール


$ composer require doctrine/dbal:

PDO でデータを登録

setUp メソッドでテスト用のデータを投入する例です。


use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\DriverManager;
class DoctrineTestCase extends \PHPUnit_Framework_TestCase
{
    public function setUp()
    {
        $parameters = [
            'driver'   => 'pdo_mysql',
            'host'     => 'localhost',
            'user'     => 'root',
            'password' => 'password',
            'dbname'   => 'database',
            'charset'  => 'utf8',
        ];
        //Doctrine 初期化
        $config = new Configuration([\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC]);
        $conn = DriverManager::getConnection($parameters, $config);
        //データ初期化
        $conn->exec('DELETE FROM user');
        //データ登録
        $sql = <<prepare($sql);
        $stmt->bindValue(':email', 'hoge@example.com');
        $stmt->bindValue(':name', 'hoge');
        $stmt->execute();
    }
}

DB 接続のパラメータはいい感じに外部ファイルに逃がしてあげると良いと思います。Symfony の Yaml コンポーネントを使って yml に設定を外出しすると Symfony っぽくなりますね。Github のサンプルはそうしてあるので気になる方はサンプルをご覧ください。

その他、テスト書くときの tips

Basic 認証を突破したい


$client = new Client();
$client->setAuth('admin', 'password');

SSL 自己署名証明書でエラーが出る


$client = new Client();
$client->getClient()->setDefaultOption('verify', false);

タイムアウト時間を伸ばしたい


$client = new Client();
$client->getClient()->setDefaultOption('config/curl/' . CURLOPT_TIMEOUT, 60);

参考サイト

まとめ

  • Symfony のファンクショナルテストに近い感覚でテストが書ける
  • ログインが必要なサイトのテストも大丈夫
  • CSS セレクタ便利
  • 各種パラメータは app/config/parameters.yml に移すと良い

Goutte でブラウザテストを書く方法を紹介しました。レガシーなプロジェクトのテストを Symfony っぽく書けるのはなかなか楽しいものです。js で DOM をガンガン書き換えている部分はテストできませんが、それ以外であれば大部分をテストできると思います。
少し問題なのが、要求される PHP バージョンが 5.3 以降なこと。レガシーな PHP に限って 5.2 で動いてたりするんですよね。状況によりますが、ブラウザテストは PHP のバージョンに依存しない RSpec + Capybara に統一するのがいいのかなと改めて思いました。