「始めよう」というタイトルを冠していながら, まったく始められる感の無いスライドになってしまいました.
元々は PHPUnit を使ったことが無いしユニットテストとかも書いたことが無い, という人に PHPUnit に興味を持ってもらうことを目標として作っていたはずなので, それを考えると駄作もいいところ.
ターゲットを明らかに見失っている.

ですが, ここ最近ずっと感じつつも, なかなか言語化できずにいたことをアウトプットできた, という意味では多いに価値があったと思います.
42 ページ目以降の「蛇足 : オレはこう思う」が主にこのスライドで言いたかったことなんだと思います.

継続的に改善を重ねることで絶対精神にプリズムジャンプ, できるような環境を作って行きたい.

,

この記事は PHP5.4 Advent Calendar jp: 2011 の 20 日目です.

Travis CI とは

Travis CI は, Continuous Integration (CI: 継続的インテグレーション) を実行するクラウド環境です.
GitHub に push すると, Travis CI の VM 上に通知が行われ, GitHub リポジトリからのチェックアウトや, ユニットテストの実行が行われます.
ユニットテストの実行は成功/失敗の結果により通知が行われ, また, 履歴も Travis CI 上に残ります.

元々は Ruby 専用のサービスだったと思いますが, その後 Clojure や Node.js などをサポートし, 1 ヶ月ぐらい前に PHP までもサポートされました.

Travis CI を利用する PHP プロジェクト

これら以外にも Symfony 関連のライブラリやバンドルは多く登録されています.

ところで, 国産のものだと今のところは Diggin_Http_Charset ぐらいしか把握していません.
日本語の情報はまだほとんど無いに等しい状況なので, 日本国内の, 少なくとも PHP コミュニティにおいてはまだあまり認知されていないように思えます.

記事にするにはまだまだ調べが足りないのですが, とりあえず日本の PHP コミュニティにも広まって欲しいと思い, 紹介してみることにしました.

Travis CI で CI するまでの手順

何はともあれ, 実際に GitHub のリポジトリを登録してみましょう.

大まかに以下のような流れとなります.

  1. GitHub でのアカウント登録
  2. PHP プロジェクトのリポジトリを用意
  3. リポジトリ内に専用の設定ファイル .travis.yml を用意
  4. GitHub アカウントでの OAuth により Travis CI にサインイン
  5. Travis CI のアカウント情報を GitHub のリポジトリの Service Hooks に登録
  6. GitHub リポジトリに git push

まずは, Travis CI にサインインします.
GitHub のアカウントさえあれば, OAuth で簡単にサインインできます.

PHP のプロジェクトは何でもいいですが, とりあえずは PHPUnit のテストが書かれているものが必要です.
ちょっと試したいけど手頃なリポジトリが無い, という場合は, 以下のリポジトリを fork してやってみるのもいいでしょう.

Travis CI 用の設定ファイル .travis.yml ファイルは, とりあえず以下のようなものを用意しましょう.
これを, リポジトリ上のルートディレクトリに配置します.

設定項目は他にもたくさんあるので, 詳細は公式ドキュメントの Building a PHP Project を参照してください.

これで, git push 時に PHP5.3 と PHP5.4 の環境でテストが実行されます.
(そういえばこの記事は PHP5.4 Advent Calendar でした)

テストの実行には明示していませんが, デフォルトでは phpunit コマンドが実行されます.
PHPUnit ではデフォルトで phpunit.xml を設定ファイルとして読み込むので, これもリポジトリルートに置いておきましょう.
(ところで, よく phpunit.xml.dist という名前にしているものがありますけど, あれってどういう意味なんでしょう…)

リポジトリの Service Hooks の指定, つまりこれは push 等のイベントの発生時に Travis CI に通知を行う機能ですが, これは Travis CI 上の画面から簡単にできます.
フリックスイッチを ON にするだけです.

リポジトリの選択

リポジトリの選択

ですが, このリポジトリ一覧は最大で 30 しか表示されない問題があるようで, 登録したいリポジトリが表示されない場合は, 手動で登録する必要があります.
手動での登録は GitHub のリポジトリから Admin -> Service Hooks -> Travis と進み, 必要な情報を入力します.

Domain と User は通常空のままでいいので, Token のみ Travis CI の画面上からコピーし, Active にチェックを入れた上で, Update Settings します.

これで Travis CI で CI を行う準備が整いました.
あとは, git push すればそのタイミングでビルドが実行されるので, Travis CI の画面上で確認しましょう.
また, GitHub の Service Hooks の画面で Test Hook ボタンを押すことでもビルドが始まります.
なお, このときは GitHub 上でデフォルトで表示される設定に鳴っているブランチ (デフォルトは master) に対してビルドが行われるようです.

Travis CI のワーカの稼働状況によってはなかなか結果が反映されないこともあるので, ご注意ください.

以下は, 細かい設定について説明します.

vendor ディレクトリへの対応

開発しているプロジェクトが依存する外部ライブラリは, よく vendor というディレクトリに入れられています.
vendor ごとリポジトリに放り込むこともできますが, 通常は vendor は .gitignore に登録して, リポジトリには登録しないことが多いでしょう.

その場合, vendor ディレクトリに対象のライブラリを自動でインストールできる必要があります.

今回は, 最近開発中の Speciphy という BDD フレームワークを例に説明を行います.

今回は, Speciphy は PHPSpec に依存しているので, 事前に PHPSpec をインストールしておかないと, Travis CI 上でテストを実行することができません.
そこで, 以下のようなインストールスクリプトを, install-vendor.sh として用意します.

今回は, PHPSpec を pyrus.phar でインストールしています.
(pyrus.phar というのは次世代の pear コマンドです)
予め必要となる pear channel を登録しておくことで, 対話無しで全自動でインストールが完了します.

なお, Symfony2 だと git submodule を利用して vendor を管理していると聞きますが, これについてはあまり知らないので割愛します.
やり方はいろいろあると思うので, フレームワークに合わせたり, 自分の好きな方法を探してみてもいいでしょう.
(Composer を使うのも面白いですね)

そして, .travis.yml に, 先ほどのインストールスクリプトを, ビルドの前の準備として実行するよう, 追記しましょう.

pyrus による vendor 対応については以下の記事を参考にしました.

Behat によるテストも行う

デフォルトでは PHPUnit によるテストが実行されるのみですが, Behat によるテストも実行できるようにします.

テストとして実行されるコマンドは, .travis.yml で script という項目にコマンドを指定することで可能です.

ところで, ビルドの成功/失敗の判定は, コマンドの終了ステータスで行われているようです.
終了ステータスが 0 なら成功, それ以外なら失敗, という具合です.

つまり, 終了ステータスをきちんと指定しているテスティングフレームワークであれば, 基本的に何でも使えることになります.

そこで, テストを実行するためのスクリプトとして, 以下のような run-test.php を用意します.

環境変数 TESTING_FRAMEWORK が PHPUnit なら PHPUnit を実行し, Behat なら Behat を phar ファイルでダウンロードして実行する, というものです.
スクリプトの最後に終了ステータスを指定して exit しているので, テストの実行が成功/失敗のいずれでも適切に通知されます.

そして .travis.yml にも再度追記を行います.

今度は env という項目を追加しています.
これは, env を配列で複数指定することによって, その数だけテストを実行するというものです.

今回は PHP のバージョンに 5.3 と 5.4 の 2 種類, テスティングフレームワークに PHPUnit と Behat の 2 種類指定しているので, 2 x 2 で合計 4 パターンのビルドが行われます.
うまくいけば, 以下のような結果が得られます.
(設定は上記のものと多少異なります)

Travis CI 実行結果

Travis CI 実行結果

PHP の Travis CI を支える技術

Travis CI では, テストを実行する際の環境の実行に phpenv が使われています.
また, PHP のビルドには当初 phpfarm が, そして今後は php-build に切り替わるようです.

どのように使われているかは, このあたりを見れば何となくわかると思います.
(ただし, master がデプロイされているとは限らないので, これが最新の状態とは限らないので注意は必要ですが)

phpenv と php-build については以前に以下のような記事を書いていますので, これらが参考になれば幸いです.

蛇足: オレはこう思う

Travis CI がどういう組織で作られているのかはまだよくわかってないのですが, GitHub のアカウントページを見る限りはそれぞれ所属はバラバラですし, IT ベンチャーとかではなく, コミュニティベースの活動による成果, ということなのでしょうか.

Travis CI の PHP 対応にあたっては, phpenvphp-build の作者である CHH さん, それらを Travis CI に取り入れるべく多くのパッチを提供した loicfrering さんを初め, 何人もの開発者が関わっているようです.

こんな風にありもののツールと, 多方面からの強力が実を結び, 今までには無い手軽さで CI 環境が手に入れられるようになったことは大変素晴らしいことだと思います.

Travis CI 自体もオープンソース活動をさらに加速させるものだと思いますし, これからの PHP コミュニティにも希望が持てると感じます.

フレームワークやライブラリを開発している皆さんは, 導入を検討してみてはいかがでしょうか.

, , , , ,

最近 HTML5 化した Slideshare ですが, エラーで表示できないスライドが多すぎてまともに閲覧できないようです.
この記事に掲載している Flash 版は今まで通りの問題なく表示できるのですが…

スライドが完成したのが勉強会への出発 15 分前で, 通しで練習することすらできず, 発表はかなりひどいものとなっていまいました.
最低限, スライドの流れぐらいは頭の中に入れて発表すべきですね…

スライドだけ見てわかるような作りにはなっていないので, 以下で補足したいと思います.
ただし, 序盤は省略し, 本論となる書法編とパターン編についてのみとなります.

それぞれ該当するスライド番号も付記してありますので, よければご活用ください.

書法編 1: ヘルパーメソッドを使う (25 ~ 30)

テスト対象のオブジェクト, それを生成するのに必要な依存オブジェクトの生成には, ヘルパーメソッドの使用を検討しましょう.
new するだけで済むものであればそれでもいいのですが, 生成に複雑なステップを踏む必要がある場合に威力を発揮します.

これは一般的なプログラミングにおけるサブルーチンみたいなものだと思って問題無いと思います.
ただし, 可読性を問題にするのであれば, 多少長くても説明的でわかりやすいメソッドの命名を心がける必要があります.

テスト対象の生成が複雑だと, 複数のテストの比較が大変になります.
ヘルパーメソッドを使うことで, テスト A とテスト B はどこに有意な違いが有り, 結果に違いが生まれるのか, ということがわかりやすくなります.

書籍 [エラー: isbn:0131495054:n というアイテムは見つかりませんでした] では, Fixture Setup Patterns において Creation Method という名前で紹介されています.
また, これの応用として, 引数付きの Parameterized Creation Method というものも紹介されています.

書法編 2: テストメソッドを分割する (31 ~ 36)

ひとつのテストメソッド内で, いろんなことをテストしようとすると, 何をテストしようとしているのかがわかりにくくなります.
例え同じメソッドを対象にしたテストであっても, 前提条件等が違うのであれば, 分割するべきです.

分割することで, それぞれのテストメソッドには, より具体的な名前をつけることができます.
これもまた可読性に寄与すると言えるでしょう.

これは癖のレベルの話なので, 普段からテストを書いている人であれば無意識にそうなると思います.
まずは 1 つのテストメソッドにおいてアサーションは 1 つまで, というルールを課すことで自然とそういう書き方に近づくと思います.

パターン編 1: 依存性は外から差し込む (39 ~ 49)

スライドでは, フレームワークによくある Request クラスを例に, $_SERVER といったスーパーグローバル変数を使ったときに起こる問題について説明しました.

$_SERVER を使った実装でもちゃんと動作するので, 問題と見なさないこともできます.
しかし, 問題はテストを書こうとしたときに顕在化します.

スーパーグローバル変数に限らず, グローバル変数を利用した実装だと, 複数のテストが影響し合う可能性が生まれます.
スライドではコードで例示しているので, 具体的にはスライドの該当箇所を参照してください.
スライドのように unset($_SERVER['HTTPS']) とすることで回避できますが, コードベースが巨大化すると, 暗黙的なコンテキストが肥大化し, テストを書くのがだんだんと困難になります.

その代わりにどうするのかというと, スーパーグローバル変数 $_SERVER を直接参照するのではなく, コンストラクタやセッターメソッド等で, 引数として差し込む, という方法を採ります.
Dependency Injection (DI, 依存性の注入) というパターンです.
DI を採用することで, 再利用性が高くなるので, テストを書くことで設計が向上する例としては比較的よく挙げられます.

パターン編 2: 外部への依存を避ける (50 ~ 62)

ひとつ前の話を逆に言っているだけですが, 別の側面から解説しています.

外部への依存としては, 例えば以下のようなものが挙げられます.

  • グローバル変数/スーパーグローバル変数
  • ファイルシステム
  • Web API やデータベース等のネットワーク

しかし, もちろんこれらを完全に避けてのアプリケーション開発は考えられません.
それではどうするのか, ということで, スライドでは Active Record の問題点と, それを解決したパターンとしての Data Mapper を例に説明しています.

ここでいう Active Record というのはパターンとしての Active Record で, 書籍 [エラー: isbn:0321127420:n というアイテムは見つかりませんでした] で紹介されています.
Ruby on Rails の ORM としての ActiveRecord は, それを実装したものです.

Active Record においては, 1 つのレコードが 1 つのオブジェクトにマッピングされ, それぞれのレコードはデータベースを知っています.
なので, $record->save() といった操作が可能になります.

Data Mapper でも 1 つのレコードが 1 つのオブジェクトに, という部分は変わりませんが, それぞれのレコードはデータベースのことを知りません.
例えば users というテーブルであれば, User クラスがその 1 レコードを表し, UserMapper はテーブルを表します.
(実際にはテーブルをラップしたクラスとして実装されることが多いと思いますが, ここでは単純化しています)
レコードはデータベースのことを知らないので, レコードを保存する際は $mapper->save($record) といったようにする必要があります.

それでは Active Record の何が問題なのか.
Active Record ではレコードがドメインロジック (ビジネスロジック) を持ったドメインオブジェクトとしての側面と, データアクセスを行うオブジェクトとしての側面を持つことになります.
それぞれは全く違った関心事なので, 関心事の分離 (SoC)単一責任原則 (SRP) といったソフトウェア開発の原則に違反することになります.
テストに関していうと, データベースへのアクセス無しにドメインロジックをテストできない, といったことが起こりやすくなります.
(もちろん, 実装によりますが)

逆に Data Mapper ではドメインロジック (上の例でいうと User クラス) をデータベース無しでテストできます.
データベースに対してテストを書きたいときもありますし, そのためのテクニックもいろいろとありますが, まずはそれらを無視してテストできるようなクラス分割を行うことで, 低コストで確実にテストを書くことができます.

パターン編 3: Singleton を避ける (63 ~ 71)

Singleton は GoF のデザインパターンの中では最も理解しやいもののひとつで, よく使われるパターンのひとつです.
しかし, 同時に批判的意見の多いパターンでもあります.

Singleton ではクラスの static プロパティとしてオブジェクトとその状態がグローバル空間に残ってしまいます.
これはつまり, グローバル変数と同様の性質を持っている, ということができます.

また, グローバル変数と違って, 容易に上書くこともできないので, テストをする上ではさらに厄介な存在といえます.

対策としては以下が考えられます.

  • 状態を初期化するためのメソッドを用意する
  • アプリケーション本体では getInstance() といった, 常に同一のインスタンスを取得するメソッドの使用を規約化し, テストでは new でそれぞれ独立したオブジェクトを生成し, テストする

また, PHPUnit にはこれらを回避するための –process-isolation オプションであったり, @runTestsInSeparateProcesses アノテーションといった機能もあるのですが, それらについては説明しませんでした.
Singleton を使わなければ, これらの機能について理解する必要もなくなりますし, これらは Slow Test (テストの実行が遅くなる) 問題を引き起こします.

Singleton で実装したくなったとき, 本当に Singleton である必要があるのかについては, 一度考えてみる必要があるでしょう.

参考文献

スライド中に挙げたものです.
書籍へのリンクにはアフィリエイトタグをつけています.

  • [エラー: isbn:0131495054 というアイテムは見つかりませんでした]
    xUnit とというのは SUnit (Smalltalk), JUnit (Java) といったナントカ Unit 系テスティングフレームの総称です.
    例として示されるコードは Java のものが多めですが, PHPUnit も書き方を含めて xUnit と呼べるものなので, この書籍で挙げられている考え方やテクニックは, ほぼそのまま適用できます.
    鈍器として人を殺せる程の分量がありますが, 気になるトピックをつまみ食いすることもできます.
  • [エラー: isbn:0470872497 というアイテムは見つかりませんでした]
    PHPUnit の作者 Sebastian Bergmann を初めとする豪華な著者陣による, PHP アプリケーションの品質, テスト, 設計についての書籍.
    Symfony の作者 Fabien Potencier による章もあります.
    ソフトウェアの品質についての解説などは他の PHP 本にはまず無いものですし, 非常に刺激的な 1 册です.
  • [エラー: isbn:0321127420 というアイテムは見つかりませんでした]
    アプリケーションのアーキテクチャパターンについての書籍.
    全体的な分量からするとほとんど読めていないのですが, スライド中の Active Record や Data Mapper について参考にしました.
  • [エラー: isbn:4798015164 というアイテムは見つかりませんでした]
    GoF によるデザインパターンを PHP に翻訳して紹介している書籍.
    新卒の頃に読んで衝撃を受けました.
    残念ながら絶版なので買うことはできませんが, 原稿が公開されているので, 読むことはできます.
    是非電子書籍化して欲しい 1 冊です.

こちらはスライドです.

  • Dependency Injection with PHP 5. 3
    
Symfony の作者 Fabien Potencier によるスライド.
    序盤は Dependency Injection の基礎についての説明で, そのあとに PHP 5.3 における DI コンテナの実装について説明しています.
  • BEAR DI
    リソース指向のフレームワーク BEAR の作者の @koriym さんによるスライド.
    序盤は前のスライドを日本語訳しつつ引用し, 後半では BEAR の DI コンテナについて説明しています.
  • Clean PHP
    PHPUnit の作者 Sebastian Bergmann によるスライド.
    これだけ見て何かを理解するのには向きませんが, Active Record と Data Mapper の比較についてはこれを参考にしました.
  • Singletons in PHP – Why they are bad and how you can eliminate them from your applications
    
Singleton の問題点について, 詳細に説明されています.
,