Skip to content

Instantly share code, notes, and snippets.

@1000k
Last active December 13, 2015 17:58
Show Gist options
  • Save 1000k/4951904 to your computer and use it in GitHub Desktop.
Save 1000k/4951904 to your computer and use it in GitHub Desktop.
Controller を TDD で開発する流れです。CakePHP 2.2.5 を使いました。
<?php
App::uses('AppController', 'Controller');
class ApisController extends AppController {
// どの Model も使わないようにする。
public $uses = false;
public function index() {
$data = array();
foreach ($_POST as $key => $value) {
$data[$key] = $value + 1;
}
$this->set($data);
}
}
<?php
App::uses('ApisController', 'Controller');
/**
* ApisController Test Case
*
*/
class ApisControllerTest extends ControllerTestCase {
/**
* Fixtures
*
* @var array
*/
public $fixtures = array(); // どの Fixture も参照しないようにする
/**
* @covers ApiController::index
*/
public function testIndex() {
// 期待される値
$expected = array(
'uso' => 801
);
// ApisController::index() を叩いた動作を再現する。
// 第2引数の return オプションに vars を指定しているので、
// $this->vars には Controller から View に渡った値が入る。
$this->testAction(
'/apis/index',
array(
'data' => array('uso' => 800), // uso=800 を POST する
'return' => 'vars'
)
);
$this->assertEquals($expected, $this->vars);
}
}

CakePHP 2 でコントローラーをテスト駆動で開発する

ApisController をテスト駆動で作ってみるチュートリアルです。

  1. ダミーのテーブルを作成する

bake コマンドはテーブルに紐付いていない MVC を作れません。 以下、Apis テーブルがあるものとして話を進めます。

XAMPP を使っているなら、下記コマンドで Mojamoja データベースに Apis テーブルを作成できます。

$ c:\xampp\mysql\bin\mysql -uroot Mojamoja
mysql> CREATE TABLE `apis` (
  `id` int(11) NOT NULL,
  `created` date,
  `modified` date,
  `name` text
) ENGINE=InnoDB;
  1. 必要なファイルをbakeする

$ cd c:/mojamoja/cake2/app
$ ./Console/cake.bat bake controller apis
$ ./Console/cake.bat bake view apis

テストしたいのは Controller だけですが、実行時に対応する View が無いとエラーになるので一緒に作っておきます。

ちなみに逆順でコマンドを叩くとエラーが出ます。View を作る前に、対応する Controller が必要なためです。

この時点でテストを動かすことが可能です。

$ Console\cake.bat test app controller/ApisController


Welcome to CakePHP v2.2.5 Console
---------------------------------------------------------------
App : app
Path: c:\work\htdocs\cake2_tdd_tutorial\app\
---------------------------------------------------------------
CakePHP Test Shell
---------------------------------------------------------------
PHPUnit 3.7.10 by Sebastian Bergmann.

�[41;37mF�[0m

Time: 2 seconds, Memory: 4.25Mb

There was 1 failure:

1) Warning
No tests found in class "ApisControllerTest".

C:\work\htdocs\cake2_tdd_tutorial\lib\Cake\TestSuite\CakeTestRunner.php:59
C:\work\htdocs\cake2_tdd_tutorial\lib\Cake\TestSuite\CakeTestSuiteCommand.php:113
C:\work\htdocs\cake2_tdd_tutorial\lib\Cake\Console\Command\TestShell.php:274
C:\work\htdocs\cake2_tdd_tutorial\lib\Cake\Console\Command\TestShell.php:259
C:\work\htdocs\cake2_tdd_tutorial\lib\Cake\Console\Shell.php:395
C:\work\htdocs\cake2_tdd_tutorial\lib\Cake\Console\ShellDispatcher.php:201
C:\work\htdocs\cake2_tdd_tutorial\lib\Cake\Console\ShellDispatcher.php:69

�[37;41m�[2KFAILURES!
�[0m�[37;41m�[2KTests: 1, Assertions: 0, Failures: 1.
�[0m�[2K

「テストケースが1個もねえよ」と怒られて終了します。 当然の結果なので次に進みましょう。

  1. テストケースを作る

ApisController::index() の仕様に沿ってテストケースを書きます。 今回は「POSTされた値に 1 プラスして返す」だけの単純なメソッドだとします。

app\Test\Case\Controller\ApisControllerTest.php は下記のようになります。

<?php
App::uses('ApisController', 'Controller');

class ApisControllerTest extends ControllerTestCase {

    public $fixtures = array();    // どの Fixture も参照しないようにする

    /**
     * @covers ApiController::index
     */
    public function testIndex() {
        // 期待される値
        $expected = array('uso' => 801);

        // ApisController::index() を叩いた動作を再現する。
        // 第2引数の return オプションに vars を指定しているので、
        // $this->vars には Controller から View に渡った値が入る。
        $this->testAction(
            '/apis/index',
            array(
                'data' => array('uso' => 800),      // uso=800 を POST する
                'return' => 'vars'
            )
        );

        $this->assertEquals($expected, $this->vars);
    }

}

この時点でテストを叩くと、当然まだ ApisController::index() が実装できていないので、エラーが出て終了します。(Red)

この後実際のコードを書いてテストを成功 (Green) にするのがテスト駆動開発です。

  1. とにかく成功するコードを書く

この作業の前に View/Apis/index.ctp の中身を全て空にしておいてください。 そうしないと、なにやら単体テスト時に余計な文字列が紛れ込んで失敗してしまいます。

app/Controller/ApisController.php を以下のように実装します。

<?php
App::uses('AppController', 'Controller');

class ApisController extends AppController {

    // どの Model も使わないようにする。
    public $uses = false;

    public function index() {
        $data = array('uso' => 801);
        $this->set($data);
    }

}

これでテストを再び実行します。今度は Green になります。

$ Console\cake.bat test app controller/ApisController


Welcome to CakePHP v2.2.5 Console
---------------------------------------------------------------
App : app
Path: c:\work\htdocs\cake2_tdd_tutorial\app\
---------------------------------------------------------------
CakePHP Test Shell
---------------------------------------------------------------
PHPUnit 3.7.10 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 6.00Mb

�[30;42m�[2KOK (1 test, 1 assertion)
�[0m�[2K

テストケース内で予測している値をそのまんま返しているので、成功するのは当たり前です。 もしこの時点で失敗した場合は、どこか別の部分で間違っている可能性があります。

この後はコードを実装しながらテストケースを走らせ、最終的に Green にします。

  1. 最終形

最終的に実装したコードは下記のようになります。

app/Controller/ApisController.php

<?php
App::uses('AppController', 'Controller');

class ApisController extends AppController {

    // どの Model も使わないようにする。
    public $uses = false;

    public function index() {
        $data = array();

        foreach ($_POST as $key => $value) {
            $data[$key] = $value + 1;
        }
        
        $this->set($data);
    }
}

実装コードを Green にする際には、「とにかく早く書くこと」が大切です。 最初から美しさを求めてコーディングするのは辞めたほうがいいでしょう。 このコードはテストケースによって保護されているので、いくらでも後から洗練できます。

このように後からコードを洗練することを リファクタリング と呼びます。

まとめ

TDDは以下のような流れで、Red->Green をリズミカルに行います。

  1. テストケースを書く。
  2. テストを走らせ、失敗することを確認する。(Red)
  3. 必ずテストが成功する実装コードを書く。(Green)
  4. 実装とテストを繰り返しながら、最終的に Green になる実装コードを書く。

ゆとり期間にコードをリファクタリングしましょう。 そうすることでコードのメンテナンス性を保つことができます。

補足

公式チュートリアル はそこそこ学べますが、なぜか結果を debug() でコンソールに表示してるだけで、アサーションをやってません。これはダメです。 戻り値はちゃんと assert しましょう。モックを使って挙動を確かめるなら expectation を使いましょう。

参考になるサイト

  • CakePHP API
    • メソッドの使い方を調べるだけならこっちの方が早いです。
  • PHPUnit 公式マニュアル
    • CakePHP2 の CakeTestCase クラスは、PHPUnit の簡単なラッパーなので、全ての機能が使えます。こちらのドキュメントも読んでおくといいです。
  • PHPUnit のアサーション一覧
    • ほとんどの場合は assertEquals() でカバーできますが、知っておくと便利なものも多いです。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment