menu
書いてる野郎
orebike@gmail.com
CakePHPでテストするにはSimpleTestという外部のライブラリを使うことになる
SimpleTestのサイト→SimpleTest - Unit Testing for PHPからダウンロードして解凍、
app/vendors
のディレクトリに突っ込んでおしまい
app/vendors/simpletest/ファイルいろいろ
みたいになるように入れる。
モデルをテストするにはまずそのテストを駆動するためのテストクラスを準備する。
今回はhoge.phpモデルをテストしたいので
app/tests/cases/models
以下にファイル
hoge.test.php
を作る。
そして、以下のようなCakeTestCaseを継承したクラスを実装する。 名前は「テスト対象モデル名TestCase」がいいだろう。
App::import('Model','Hoge'); class HogeTestCase extends CakeTestCase{ var $fixtures = array('app.hoge'); function startCase(){ echo "■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■<br />"; echo "HogeTestCase<br />"; echo "■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■<br />"; } function startTest($method){ $this->Hoge = ClassRegistry::init('Hoge'); echo "=================================================================<br />"; echo "start {$method}<br />"; echo "=================================================================<br />"; } function testFindById(){ $result = $this->Hoge->findById(1); $this->assertTrue($result['Hoge']['id'] == 1); } function endTest(){ unset($this->Hoge); ClassRegistry::flush(); } function endCase(){ } }
テストの実行。
webrootにある
test.php
にアクセスする。Hogeモデルのテストクラスをつくったので のようにTestCasesをクリックするとモデルごとのテストケース一覧が見えるようになると思う。
config/core.phpのdebugが0になっているとtest.phpにアクセスできないので見れない場合は確認しておく。
このモデルをクリックすることでテストクラスのメソッドが一気に実行される。失敗が一個でもああれば赤表示、全部成功でグリーン。
自動的にテストするといっても試行錯誤する部分もあると思うので画面出力が欲しい場合がある。 そういう場合は単に
echo "hoge";
とechoしておけばechoの出力をCakeTestCase側がトラップしてくれてテストの結果出力と共に出してくれるようになる。
CakePHPにはグローバルな関数としてprというデバッグに便利な関数が用意されているのでこいつを利用すると楽に短く書ける
pr($hoge);
このprという関数はprint_rの出力をpreタグで挟み込んでくれるようなものでテスト画面では重宝する。
startTestは各テストメソッドが実行される前の処理を記述する。つまりテストメソッドが10個あれば10回実行される。
モデルクラスのオブジェクト化等の初期化処理を共通的に書いておけばいいだろう。
テストの出力には切れ目が無いので、区切りを入れておくのもいいと思う。第一引数にテストメソッド名を受けるのでこれも利用できる。
function startTest($method){ $this->Hoge = ClassRegistry::init('Hoge'); echo "=================================================================<br />"; echo "start {$method}<br />"; echo "=================================================================<br />"; }
頭にtestとついたメソッドは実際に実行されるテストそのものとなる。
テストメソッド終了ごとに実行される処理を記述するstartTestと対になるメソッド。
オブジェクトの破壊処理とかを行う
function endTest(){ unset($this->Hoge); ClassRegistry::flush(); }
テストの判定はテストメソッド中で
$this->assertTrue(true);
のようなassert系メソッドを使う。 一度でもassertが失敗を返すとテストとしては失敗になる。
判定 | 記述 | メモ |
---|---|---|
配列の要素の一致(順番無視) | $this->assertTrue($a == $b); | ==演算子は配列の判定で順番を無視して入っている要素が一致すればtrueになる |
配列の要素の一致(順番も一致) | $this->assertTrue($a === $b); | ===演算子は要素も順番も完全に一致すればtrueになる |
NULLである | $this->assertNull($a); | これは厳密にnullをチェックする。ゼロやfalseでは成功しない |
CakePHPはデータベースと連携したテストもサポートしている。
まず突っ込む用のデータを用意する。データは↓のような場所に「任意の名前_fixture.php」というファイル名で作る。この名前は本当になんでもいい
app/tests/fixtures/hoge_fixture.php
中身はのようにCakeTestFixtureクラスを継承した↑の「任意の名前Fixture」クラスを作る
class HogeFixture extends CakeTestFixture { var $name = 'Hoge'; var $import = array('model' => 'Hoge'); var $records = array( array('id' => 1, 'name' => 'うんこ', 'age' => '32', 'created' => '2011-08-12 00:00:00', 'modified' => '2011-08-12 00:00:00', ), array('id' => 2, 'name' => 'カレー', 'age' => '11', 'created' => '2011-08-12 00:00:00', 'modified' => '2011-08-12 00:00:00', ), ); }
nameプロパティは名前・・・、
improtプロパティでmodelを指定してその設定を使って入れるデータの形式を取得している。つまりデータの形式をつどモデルが指し示すtableの定義から引き直してくれるのだ。なので入れるデータ形式をfixtureでしてしなくても大丈夫になっている。
このモデルが指し示すtableはCakePHP1.3では常にdatabase.phpに指定してあるdefaultを使うことになっていて、testの設定に空っぽのDBを用意しておけばそこにcreate tableまでしてくれてその後にdropまでやってくれるのだ。至れり尽くせり。
なので開発機の設定はdefaultも含めて全部テスト用と割りきって開発したほうがよい。環境による使い分けはデプロイ時にdababase.phpを取り替えてしまったほうが安全だろう。
そしてrecordsプロパティに連想配列の配列として値を書いておく
空っぽのテーブルが必要でデータは必要ないという場合でもfixtureがないとテーブル自体がないのでレコード空で作っておく必要がある。
ここでは設定をimportという形でもってきたが、CakePHPでは全てのモデルのidがauto_incrementでPKに指定されているという前提になっている。 つまりModelからimportした場合はかならずそのようにtableが構築される。マスタなどは開発者が任意のidとつけているということもあり、順番や値が重要だったりする。
そのようなid直指定の値をfixtureから流し込もうとするとauto_incrementの設定と被ってDupricateエラーが発生することがある。この場合fixtureで構築するテーブル構造を全部指定すれば回避できる。
そう、idのフィールドにkey⇒primaryを付けないのだ。
さっきのデータをテストに組み込むにはこのようにfixturesプロパティにぶち込む、複数読み込む必要がある場合は列挙する。
class HogeTestCase extends CakeTestCase { var $fixtures = array('app.hoge'); //.... }
この時指定する名前は「app.任意の名前」だ。「app.」は決まり文句みたいなもんでアプリ側のfixtureから読み込みますよという印みたい。 あまり使う機会はないと思うがCakePHPそのもののテストをしたい時はcore.になる。
fixturesを用いた初期化はstartTestのさらに前の処理のbeforeという段階で行われるので実際のテストメソッド中で動的に切り替えたりすることはできない。
逆に言うとテストデータの状態をリセットしたい時にテストメソッドを分けるということだな。
function startTest($method){ $this->Hoge = ClassRegistry::init('Hoge'); }
この時点でinitメソッドで作られたオブジェクトは接続先が自動的にtestに向けられている。なのでfixtureによって作られたデータでテストできるというわけ。
この設定は内部ではClassRegistryのconfigメソッドを使ってやられているのだが、このconfigメソッドがModelのコンストラクタで接続先を指定している。何もしなければやっぱりdefaultになる。
このコンストラクタをオーバーライドして適切にスーパークラスを呼び出していないような実装(呼び出さないとか、パラメータを受けない、パラメータを渡さない)をしている場合ここがうまく機能せずテストがムチャクチャになる
これで結構嵌った。
CakePHPのテストは全自動で
までやってくれるのでガワだけあれば特に後は何も必要ない。
テストによってfixtureを使い分けたくなることもあるだろ。別にCakePHPのテストでは1モデルクラスに対して1テストケースクラスと規定しているわけじゃないし、テストケースクラスが対応するモデルを固定しているわけでもない。
なのでテストケースクラスをたくさん作って内部で呼ぶfixtureを変えてやればよいだけ。
テストをまとめて一気に実行するにはテストのグルーピング機能を使う
app/tests/group/my_all.group.php
のようなファイルを作って中に
class MyAllGroupTest extends TestSuite { var $label = 'My All Group Test'; function myAllGroupTest() { //まとめて実行したい対象を死ぬほどズラズラ書く TestManager::addTestFile($this, APP_TEST_CASES.DS.'libs'.DS.'hoge'); TestManager::addTestFile($this, APP_TEST_CASES.DS.'models'.DS.'piyo'); } }
このようにする。
幾つかの解説(公式でも!)でTestSuiteクラスではなくGroupTestクラスを継承しているものがあるが・・・
「グループするにはGroupTestを継承すると言ったな。あれは嘘だ」
ってな感じで嘘みたい。TestSuiteクラスを継承しましょう。このグループ機能ってアバウトに作られていてGroupTestを継承しようがaddTestFileでファイルがなかろうが別になんのエラーも出ないということ。なんだこれ
そして書いたらいつもの通り画面から実行できるわけだが・・・
App Test Groups
のほうを選ぶとさっきのやつが出てくるので選ぶと実行される
この中に
All tests
という項目があって全部のテストを一気に実行してくれるっぽいのだが順番はメチャクチャだしなんだか挙動も変なので使わないほうがいい
find系のメソッドはパラメーターのキーを間違えてもエラーにならない。単に無視されるだけ。 無視された場合SELECTならばWHERE句等が無いのと同じ挙動になる。
こういう場合fixtureの先頭レコードとかをテストの検証値に使うと条件がなくても出てきてしまうことがあるのでこのようなバグを検知できなかったりする。
fixtureのファイル名は「名前_番号_fixture.php」とし
hoge_01_fixture.php
レコードの内容を明記する。
これはなんでもかんでもわけるって意味じゃなくて分ける必要になったら分けるということ、 問題がないなら極力1個のfixtureに入れる。
テストケース側ではfixture指定の部分でそのfixtureを使用する理由を明記する。
基本的なマスタデータのような最初に管理者が作ってしまったら明示的に増やすまで増えも減りもしないテーブルデータに関しては 番号省略のファイル名にする。