単体テスト
Codeceptionはテストの実行環境としてPHPUnitを使用しています。したがって、PHPUnitのどのテストでもCodeceptionのテストスイートに追加できますし、実行できます。 いままでにPHPUnitテストを書いていたならば、これまで書いてきたようにするだけです。 Codeceptionは簡単な共通処理に対して、すばらしいヘルパー機能を追加しています。
単体テストの基礎はここでは割愛する代わりに、単体テストにCodeceptionが追加する特徴の基礎知識をお伝えしましょう。
もう一度言います: テストを実行するためにPHPUnitをインストールする必要はありません。Codeceptionも実行できます。
テストを作成する
Codeceptionは簡単にテストを作成する、すばらしいジェネレーターを持っています。
\PHPUnit_Framework_TestCase
クラスを継承している従来のPHPUnitテストを生成するところから始める事ができます。
以下のようなコマンドで生成されます。
$ php codecept.phar generate:phpunit unit Example
Codeceptionは一般的な単体テストのアドオンを備えています、それでは試してみましょう。 Codeceptionの単体テストを作成するには別のコマンドが必要です。
$ php codecept.phar generate:test unit Example
どちらのテストもtests/unit
ディレクトリに新しくExampleTest
ファイルを作成します。
generate:test
によって作成されたテストは、このようになります。
<?php
use Codeception\Util\Stub;
class ExampleTest extends \Codeception\TestCase\Test
{
/**
* @var UnitTester
*/
protected $tester;
// 各テスト前に実行される
protected function _before()
{
}
// 各テスト後に実行される
protected function _after()
{
}
}
?>
このクラスは、はじめから_before
と_after
のメソッドが定義されています。それらは各テスト前にテスト用のオブジェクトを作成し、終了後に削除するのに使用できます。
ご覧の通り、PHPUnitとは違い、setUp
とtearDown
メソッドがエイリアス: _before
, _after
されています。
実際にはsetUp
とtearDown
メソッドは、親クラスの\Codeception\TestCase\Test
クラスに実装されており、さらに単体テストの一部として実行できるように、Ceptファイルからすべてのすてきなアクションを持ったUnitTesterクラスがセットアップされています。受け入れテストや機能テストのように、unit.suite.yml
の設定ファイルの中でUnitTester
クラスが使う適切なモジュールを選べます。
# Codeception Test Suite Configuration
# suite for unit (internal) tests.
class_name: UnitTester
modules:
enabled: [UnitHelper, Asserts]
従来の単体テスト
Codeceptionの単体テストは、PHPUnitで書かれているのとまったく同じように書かれています。:
<?php
class UserTest extends \Codeception\TestCase\Test
{
public function testValidation()
{
$user = User::create();
$user->username = null;
$this->assertFalse($user->validate(['username']));
$user->username = 'toolooooongnaaaaaaameeee';
$this->assertFalse($user->validate(['username']));
$user->username = 'davert';
$this->assertTrue($user->validate(['username']));
}
}
?>
BDD Spec テスト
テストを書くときは、アプリケーションにおける一定の変化のためにテストを準備する必要があります。テストは読みやすく維持されやすくするべきです。あなたのアプリケーションの仕様が変わったら、同じようにテストもアップデートされるべきです。ドキュメントのテストにおいてチーム内部で話し合いが持たれなかったのならば、新しい機能の導入によってテストが影響を受けるということを理解していくのに壁があるでしょう。
そのため、アプリケーションを単体テストで網羅するだけでなく、テスト自体を説明的に保っておくことはとても重要な事です。私たちは、シナリオ駆動の受け入れテストと機能テストでこれを実践しています。そして、単体テストや結合テストにおいても同様にこれを実践するべきです。
この場合において、単体テスト内部の仕様を書いているSpecify (pharパッケージしている)というスタンドアロンのプロジェクトを用意しています。
<?php
class UserTest extends \Codeception\TestCase\Test
{
use \Codeception\Specify;
public function testValidation()
{
$user = User::create();
$this->specify("username is required", function() {
$user->username = null;
$this->assertFalse($user->validate(['username']));
});
$this->specify("username is too long", function() {
$user->username = 'toolooooongnaaaaaaameeee';
$this->assertFalse($user->validate(['username']));
});
$this->specify("username is ok", function() {
$user->username = 'davert';
$this->assertTrue($user->validate(['username']));
});
}
}
?>
specify
のコードブロックを使用する事で、テストを細かい単位で説明することができます。このことはチームの全員にとってテストがとても見やすく、理解しやすい状態にしてくれます。
specify
ブロックの内部にあるコードは独立しています。上記の例だと、$this->user
(他のどんなオブジェクトやプロパティでも)への変更は他のコードブロックに反映されないでしょう。
あなたはBDD-styleのアサーションをするために、Codeception\Verifyも追加するかもしれません。もし、あなたがassert
の呼び出しの中で、引数のどちらが期待している値で、どちらが実際の値なのかをよく混同してしまうなら、この小さなライブラリはとてもすばらしく可読性に長けたアサーションを追加します。
<?php
verify($user->getName())->equals('john');
?>
モジュールを使う
シナリオ駆動の機能テストや受け入れテストの中で、あなたはアクタークラスのメソッドにアクセスできました。もし結合テストを書く場合は、データベースをテストするDb
モジュールが役に立つかもしれません。
# Codeception Test Suite Configuration
# suite for unit (internal) tests.
class_name: UnitTester
modules:
enabled: [Db, UnitHelper]
UnitTesterのメソッドにアクセスする事で、テストの中でUnitTester
のプロパティを使用できます。
データベーステスト
それでは、どのようにデータベースのテストができるのか、見て行きましょう:
<?php
function testSavingUser()
{
$user = new User();
$user->setName('Miles');
$user->setSurname('Davis');
$user->save();
$this->assertEquals('Miles Davis', $user->getFullName());
$this->tester->seeInDatabase('users', array('name' => 'Miles', 'surname' => 'Davis'));
}
?>
単体テストでデータベース機能を有効にするためには、unit.suite.yml設定ファイルにて有効なモジュール一覧に Db
モジュールが含まれていることを確認してください。
受け入れテストや機能テストのように、データベースはテストが終了するごとに、クリーンにされて構築されるでしょう。
それが必要のない振る舞いであれば、現在のスイートのDb
モジュールの設定を変更してください。
モジュールにアクセスする
Codeceptionはこのスイートにおいて、すべてのモジュールに定義されたプロパティとメソッドにアクセスする事を許可しています。このときはUnitTesterクラスを使うときとは違い、直接モジュールを使用する事で、モジュールのすべてのパブリックなプロパティへのアクセスを得られます。
たとえば、もしSymfony2
を使うなら、このようにSymfonyのコンテナにアクセスします:
<?php
/**
* @var Symfony\Component\DependencyInjection\Container
*/
$container = $this->getModule('Symfony2')->container;
?>
すべてのパブリックな変数は、そのモジュールに対応するリファレンスにリストされています。
Cest
PHPUnit_Framework_TestCase
を継承したテストケースの代わりに、Codeception仕様のCest形式を使用できるでしょう。他のどのクラスも継承する必要はありません。このクラスのすべてのパブリックなメソッドがテストです。
上記の例をシナリオ駆動の形式でこのように書きなおすことができます:
<?php
class UserCest
{
public function validateUser(UnitTester $t)
{
$user = $t->createUser();
$user->username = null;
$t->assertFalse($user->validate(['username']);
$user->username = 'toolooooongnaaaaaaameeee';
$t->assertFalse($user->validate(['username']));
$user->username = 'davert';
$t->assertTrue($user->validate(['username']));
$t->seeInDatabase('users', ['name' => 'Miles', 'surname' => 'Davis']);
}
}
?>
$t
変数でアクセスしているであろう、UnitTesterクラスのいつものアサーションを追加するAsserts
モジュールを単体テストのために追加するかもしれません。
# Codeception Test Suite Configuration
# suite for unit (internal) tests.
class_name: UnitTester
modules:
enabled: [Asserts, Db, UnitHelper]
スタブ
Codeceptionは、スタブを簡単に作成するPHPUnitモックフレームワークの小さいラッパーを提供しています。\Codeception\Util\Stub
を追加して、ダミーオブジェクトの作成を始めてください。
この例では、コンストラクタを呼び出さずにオブジェクトを初期化し、getName
メソッドがjohnという値を返すように置き換えています。
<?php
$user = Stub::make('User', ['getName' => 'john']);
$name = $user->getName(); // 'john'
?>
スタブはPHPUnitのモックフレームワークから生成されます。Mockery(Mockery moduleとセット)、AspectMock、など他のものを代わりに使用することもできます。
スタブのユーティリティクラスの全リファレンスはここを見てください。
まとめ
テストスイートの中で、PHPUnitのテストはfirst-class citizensです。単体テストを書いて実行したいときはいつでも、PHPUnitをインストールする必要はなく、そのままCodeception上で実行することができます。いくつかのすばらしい特徴は、Codeceptionモジュールを統合する事で共通の単体テストを追加できることです。単体テストや結合テストのほとんどにおいて、PHPUnitのテストだけで十分です。PHPUnitのテストは速く、維持しやすいからです。