[CakePHP] Controllerのテストを書くときに気をつけること

ようやく、自分の中でテスト駆動開発するのが「デフォルト」になってきた。
そして今回、初めてMVCなシステムのテストを書いていることに気づいたので、そのポイント(一部、CakePHP限定)を書いておこうと思う。

Controllerのテストは書きにくい?

テスト駆動開発初心者でも、getter/setterなメソッドのテストは書きやすいと思われる。なぜなら、入力と出力が分かりやすいから。
しかし、Controllerのactionなメソッドは書きづらい/書けないと思っている人が多いのかなと思う。入力→出力の課程でいろいろ(むしろ、ありとあらゆることを?)やるから。


そこはやはり、モノの見方を改めるに限る。
「Controllerはテストが書きづらい/書けない」じゃなく、「テストが書けるようにControllerを書く」だ。
そもそも、テスト駆動開発自体がそういう思想の元に行われていると自分は考えている。

Controllerのテストの基本形

具体的にはどうするか?
CakePHPでは、Controllerのテスト用に、testAction()というメソッドを用意しているので、もちろんこれを使う。
Controllerの「入力」は何か?それはリクエスト(ControllerのURL)だ。では「出力」は?それはもちろん、レスポンスだ。
というわけで、単純にHTMLを返すだけのControllerの場合はこういうテストが基本形になる。

// SamplesController.php
class SamplesController extends AppController {
  public function index() {
    $this->set('title_for_layout', 'サンプル一覧');
    return $this->render(); // (1)
  }
}

// SamplesControllerTest.php
class SamplesControllerTest extends ControllerTestCase {
  public function testIndex() {
    $this->testAction('/samples/index');
    $this->assertTextContains('サンプル一覧', $this->view); // (2)
  }
}

第一のポイントは、(1)を省略せずに書くこと。そして、しっかり"return"と入れること。
CakePHPではこれをわざわざ書かなくても同じことをしてくれるが、きっちりreturnしてやることで、$this->view から取得出来るようになる。そうしたら、想定したHTMLが入っているか?チェックすることが出来るようになる。


Controllerのもう一つの主要な役割は、必要に応じて適切な遷移先へ振り分けてやること。
これはこんなふうに書く。

// SamplesController.php
class SamplesController extends AppController {
  public function add() {
    $this->set('title_for_layout', '登録画面');

    if ($this->request->is('post')) {
      if ($this->Sample->create($this->request->data)) {
        $this->Session->setFlash('正常に登録しました。');
        return $this->redirect(array('controller' => 'home'));
      }
    }
    return $this->render();
  }
}

// SamplesControllerTest.php
class SamplesControllerTest extends ControllerTestCase {
  public function testAdd_get() {
    $this->testAction('/samples/add', array('method' => 'get')); // (3)
    $this->assertTextContains('登録画面', $this->view);
  }
  
  public function testAdd_post() {
    $this->testAction('/samples/add');
    $this->assertContains('/home', $this->headers['Location']); // (4)
    $flash = CakeSession::read('Message.flash'); // (5)
    $this->assertTextContains('正常に登録しました。', $flash['message']);
  }
}

CakePHPの典型的な登録系画面のコントローラでは上記のように、GETリクエストとPOSTリクエストとで、処理内容を変える。
testAction()はPOSTリクエストがデフォルトの動作なので、GETリクエストのときは(3)のように指定する。
そして、リダイレクトのときは(4)のようにheadersの中身をチェックしてやる。リダイレクトだと $this->set() ではメッセージを渡せないので、セッションに入れてやりそれをチェックする(5)。


このように書いていくと、Controllerのテスト作成と画面/画面遷移設計が同義になって、良い感じじゃないだろうか。よし、この線で進めよう。