適切なテストが行われるように、既存の Laravel 4 アプリケーションを書き直しています。AccountController
簡単に言うと、 TDD メソッドを使用してクラスを書き直しましたが、少し頭が痛くなりました。
ユーザーのリストを含むページをレンダリングする次のメソッドを検討してください。
public function getIndex()
{
// build the view
//
return \View::make('account.list-users')
->with('users', \Sentry::getUserProvider()->findAll());
}
Smarty を使用してビューをレンダリングし、認証のために Sentry を使用しています。
ここで、次のようなテストをいくつか書きたいと思います。
public function test_getIndex()
{
// arrange
//
// set up some mocks here...
// act
//
$response = $this->client->request("GET", "/list-users");
// assert
//
// test for <table class="table">
$this->assertFalse($response->filter("table.table")==null, "table not found");
// test for some <a> tags for the "update" buttons
$element = $response->filter("td a")->first()->extract(array("href", "class", "_text"));
$this->assertTrue(strstr($element[0][0],"/my-update-url")!="");
$this->assertTrue(strstr($element[0][1],"btn btn-xs btn-success")!="");
$this->assertTrue(strstr($element[0][2],"Active")!="");
// test for some other markup...
}
私は Jeffrey Way の著書Laravel Testing Decodedに従い、上記のようなテストを書きましたが、問題なく動作します。
頭痛の種は、「ここにいくつかのモックを設定してください...」セクションで発生します。特に、設定する必要があるモックの数はばかげています。これは、より大きな Web アプリケーションの一部として、現在のユーザー モデル、メニュー構造、アラート メッセージ、ニュース メッセージ、アプリケーションのバージョン番号などのデータを View モデルに追加する View コンポーザを使用しているためです。テスト用に「必要最小限の」テンプレートを使用して、これらの多くを切り取りましたが、まだ多くのものがあります。この単純な 1 行のメソッドをテストするために、何百行ものコードを記述しているほどです。
これを行うより良い方法はありますか?
私の見方では、これを行うには2つの方法があります。
A. 私がやってきた方法
B.\View::make
すべてのテンプレート レンダリングがバイパスされるように呼び出しをモックする - このようなもの
public function test_getIndex()
{
// arrange
//
$userList = "this is a list of users";
$userProvider = Mockery::mock("\Cartalyst\Sentry\Users\Eloquent\Provider");
\Sentry::shouldReceive("getUserProvider")
->once()
->andReturn($userProvider);
$userProvider->shouldReceive("findAll")
->once()
->andReturn($userList);
$view = Mockery::mock("\Illuminate\View\View");
\View::shouldReceive("make")
->with("account.list-users")
->once()
->andReturn($view);
$view->shouldReceive("with")
->with("users", $userList)
->once()
->andReturn($view);
$view->shouldReceive("render")
->once()
->andReturn("results");
// act
//
$response = $this->call("GET", "/list-users");
// assert
//
$this->assertResponseOk();
}
このアプローチを採用すると、テストははるかに簡単になり、コントローラー メソッドに実際にあるコードのみをテストしますが、そのルートの呼び出しに関連するすべてを実際にテストするわけではありません (これは良いことかもしれませんし、そうでないかもしれません)。 -よくわかりません)そして、十分な補償を受けられないのではないかと心配しています.
では、これを行う最善の方法は何ですか: (A)、(B)、または他の何か?
編集
私のコントローラーメソッドのテストに関して、私の側にはかなりの混乱があります。これは、@TheShiftExchangeの回答と以下のコメントによって明確になりました。編集として、ここで問題に対処しようとします。これにより、質問について話し合う余地が少し増えるからです。
以下の回答にある 2 番目の例を考えてみましょう。
public function testMethod()
{
$this->call('GET', '/list-users');
$this->assertViewHas('users', \Sentry::getUserProvider()->findAll());
}
このテストを実行すると機能しますが、データベースにアクセスします。これは、いくつかのものをモックして回避しようとしていました。
したがって、このテストを少し拡張できます。
public function testMethod()
{
\Sentry::shouldReceive("getUserProvider")
->once()
->andReturn($userProvider);
// plus a mock of the UserProvider class,...
$this->call('GET', '/list-users');
$this->assertViewHas('users', \Sentry::getUserProvider()->findAll());
}
コントローラー メソッドに必要なモックに加えて、ビュー コンポーザーのコードにもモックが必要になるため、このテストは機能しません。このコードには、とりわけ次のものが含まれます$currentUser = \Sentry::getUser()
(ユーザーの名前は、アプリケーションのページの右上隅に表示されます)。
したがって、コードは実際には次のようになります。
public function testMethod()
{
\Sentry::shouldReceive("getUserProvider")
->once()
->andReturn($userProvider);
// plus a mock of the UserProvider class,...
// plus a mock of ThisClass
// and a mock of ThatClass
// and a mock of SomeOtherClass
// etc.
// etc.
$this->call('GET', '/list-users');
$this->assertViewHas('users', \Sentry::getUserProvider()->findAll());
}
そして、それはすぐに手に負えなくなります。
これは、私が何か間違ったことをしていることを示唆していますが、何が間違っているのかわかりません。この問題は、ここで何をテストしているのか正確にわからないことが原因であると思われます。
それで、結局のところ、質問は次のようになります。
コントローラーのメソッドをテストしているときに、実際にテストしようとしているのは何ですか?
コントローラのメソッドのコード? または、
リクエストからレスポンスまでの全プロセス?
テストしたいのは最初の項目です。コントローラー メソッドのコードだけです。私の質問の例は非常に単純ですが、ユーザー入力に基づいてフォームの検証やリダイレクトなどを行うコントローラーメソッドがいくつかあります。そのコードをテストしたいと思います。
を介してコードをテストするのではなく、$this->call()
単にコントローラ メソッドを直接呼び出す必要があるのでしょうか。