View on GitHub

Codeception.docs.ja

Codeception docs japanese translation

Download this project as a .zip file Download this project as a tar.gz file

単体テスト

Codeceptionはテストの実行環境としてPHPUnitを使用しています。したがって、PHPUnitのどのテストでもCodeceptionのテストスイートに追加できますし、実行できます。 いままでにPHPUnitテストを書いていたならば、これまで書いてきたようにするだけです。 Codeceptionは簡単な共通処理に対して、すばらしいヘルパー機能を追加しています。

単体テストの基礎はここでは割愛する代わりに、単体テストにCodeceptionが追加する特徴の基礎知識をお伝えしましょう。

もう一度言います: テストを実行するためにPHPUnitをインストールする必要はありません。Codeceptionも実行できます。

テストを作成する

Codeceptionは簡単にテストを作成する、すばらしいジェネレーターを持っています。 \PHPUnit_Framework_TestCaseクラスを継承している従来のPHPUnitテストを生成するところから始める事ができます。 以下のようなコマンドで生成されます。

php codecept generate:phpunit unit Example

Codeceptionは一般的な単体テストのアドオンを備えています、それでは試してみましょう。 Codeceptionの単体テストを作成するには別のコマンドが必要です。

php codecept generate:test unit Example

どちらのテストもtests/unitディレクトリーに新しくExampleTestファイルを作成します。

generate:testによって作成されたテストは、このようになります。

<?php
use Codeception\Util\Stub;

class ExampleTest extends \Codeception\Test\Unit
{
   /**
    * @var UnitTester
    */
    protected $tester;

    // 各テスト前に実行される
    protected function _before()
    {
    }

    // 各テスト後に実行される
    protected function _after()
    {
    }
}

このクラスは、はじめから_before_afterのメソッドが定義されています。それらは各テスト前にテスト用のオブジェクトを作成し、終了後に削除するのに使用できます。

ご覧の通り、PHPUnitとは違い、setUptearDownメソッドがエイリアス: _before, _afterされています。

実際にはsetUptearDownメソッドは、親クラスの\Codeception\TestCase\Testクラスに実装されており、さらに単体テストの一部として実行できるように、Ceptファイルからすべてのすてきなアクションを持ったUnitTesterクラスがセットアップされています。受け入れテストや機能テストのように、unit.suite.ymlの設定ファイルの中でUnitTesterクラスが使う適切なモジュールを選べます。

# Codeception Test Suite Configuration

# suite for unit (internal) tests.
class_name: UnitTester
modules:
    enabled:
        - Asserts
        - \Helper\Unit

従来の単体テスト

Codeceptionの単体テストは、PHPUnitで書かれているのとまったく同じように書かれています。:

<?php
class UserTest extends \Codeception\Test\Unit
{
    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']));           
    }
}

モジュールを使う

シナリオ駆動の機能テストや受け入れテストの中で、あなたはアクタークラスのメソッドにアクセスできました。もし結合テストを書く場合は、データベースをテストするDbモジュールが役に立つかもしれません。

# Codeception Test Suite Configuration

# suite for unit (internal) tests.
class_name: UnitTester
modules:
    enabled:
        - Asserts
        - Db
        - \Helper\Unit

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', ['name' => 'Miles', 'surname' => 'Davis']);
}

単体テストでデータベース機能を有効にするためには、unit.suite.yml設定ファイルにて有効なモジュール一覧にDbモジュールが含まれていることを確認してください。 受け入れテストや機能テストのように、データベースはテストが終了するごとに、クリーンにされて構築されるでしょう。 それが必要のない振る舞いであれば、現在のスイートのDbモジュールの設定を変更してください。

フレームワークとやりとりする

もしプロジェクトがすでにデータベースとのやりとりのためにORMを利用しているのであれば、データベースに直接アクセスすべきではないはずです。 テストの中で直接ORMを使ってみませんか?LaravelのORMであるEloquentを使ってテストを書いてみましょう。そのためにはLaravel5モジュールを設定する必要があります。amOnPageseeといったWebに対する振る舞いは必要ないため、ORMのみ有効化しましょう:

class_name: UnitTester
modules:
    enabled:
        - Asserts
        - Laravel5:
            part: ORM
        - \Helper\Unit

機能テストと同じように、Laravel5モジュールをインクルードしました。実際のテストでの使い方を見てみましょう:

<?php
function testUserNameCanBeChanged()
{
    // フレームワークからユーザーを作成、このユーザーはテスト後に削除される
    $id = $this->tester->haveRecord('users', ['name' => 'miles']);
    // モジュールにアクセスする
    $user = User::find($id);
    $user->setName('bill');
    $user->save();
    $this->assertEquals('bill', $user->getName());
    // フレームワークの関数を使ってデータが保存されたことを検証する
    $this->tester->seeRecord('users', ['name' => 'bill']);
    $this->tester->dontSeeRecord('users', ['name' => 'miles']);
}

ActiveRecordパターンで実装されたORMを持つすべてのフレームワークにおいて、とても良く似たアプローチをとることができます。 それらはYii2やPhalconで、同じように動作するhaveRecordseeRecorddontSeeRecordを持っています。機能テスト用のアクションを利用しないよう、part: ORMを指定してインクルードしてください。

DoctrineとともにSymfonyを利用するケースでは、Symfonyそのものは有効とせず、Doctrine2のみを利用するようにしてください。:

class_name: UnitTester
modules:
    enabled:
        - Asserts
        - Doctrine2:
            depends: Symfony
        - \Helper\Unit

このようにすることで、Doctrineはデータベースへの接続確立のためにSymfonyを使いながら、Doctrine2モジュールのメソッドを利用することができます。このケースではテストは次のようになります。:

<?php
function testUserNameCanBeChanged()
{
    // フレームワークからユーザーを作成、このユーザーはテスト後に削除される
    $id = $this->tester->haveInRepository('Acme\DemoBundle\Entity\User', ['name' => 'miles']);
    // モジュールにアクセスしてEntity Managerを取得する
    $em = $this->getModule('Doctrine2')->em;
    // 実際のユーザーを取得する
    $user = $em->find('Acme\DemoBundle\Entity\User', $id);
    $user->setName('bill');
    $em->persist($user);
    $em->flush();
    $this->assertEquals('bill', $user->getName());
    // フレームワークの関数を使ってデータが保存されたことを検証する
    $this->tester->seeInRepository('Acme\DemoBundle\Entity\User', ['name' => 'bill']);
    $this->tester->dontSeeInRepository('Acme\DemoBundle\Entity\User', ['name' => 'miles']);
}

どちらの例においても、テスト間におけるデータの永続化について心配する必要はありません。 Doctrine2、Laravel5、どちらのモジュールにおいてもテストの終了時に作成されたデータはクリーンアップされます。 これは、トランザクションでテストをラッピングし、その後、それをロールバックすることによって行われます。

モジュールにアクセスする

Codeceptionはこのスイートにおいて、すべてのモジュールに定義されたプロパティとメソッドにアクセスする事を許可しています。このときはUnitTesterクラスを使うときとは違い、直接モジュールを使用する事で、モジュールのすべてのパブリックなプロパティへのアクセスを得られます。

この話は、先ほどのコードでDoctrine2モジュールからEntity Managerへのアクセスを行ったように、すでに検証しました。

<?php
/** @var Doctrine\ORM\EntityManager */
$em = $this->getModule('Doctrine2')->em;

もしSymfonyを使うなら、このようにSymfonyのコンテナにアクセスします:

<?php
/** @var Symfony\Component\DependencyInjection\Container */
$container = $this->getModule('Symfony')->container;

有効化されているモジュールのすべてのPublicプロパティについても同じようにアクセスすることができます。アクセス可能なプロパティはモジュールリファレンスに列挙されています。

BDD Specテスト

テストを書くときは、アプリケーションにおける一定の変化のためにテストを準備する必要があります。テストは読みやすく維持されやすくするべきです。あなたのアプリケーションの仕様が変わったら、同じようにテストもアップデートされるべきです。ドキュメントのテストにおいてチーム内部で話し合いが持たれなかったのならば、新しい機能の導入によってテストが影響を受けるということを理解していくのに壁があるでしょう。

そのため、アプリケーションを単体テストで網羅するだけでなく、テスト自体を説明的に保っておくことはとても重要な事です。私たちは、シナリオ駆動の受け入れテストと機能テストでこれを実践しています。そして、単体テストや結合テストにおいても同様にこれを実践するべきです。

この場合において、単体テスト内部の仕様を書いているSpecify(pharパッケージに含まれている)というスタンドアロンのプロジェクトを用意しています。

<?php
class UserTest extends \Codeception\Test\Unit
{
    use \Codeception\Specify;

    private $user;

    public function testValidation()
    {
        $this->user = User::create();

        $this->specify("username is required", function() {
            $this->user->username = null;
            $this->assertFalse($this->user->validate(['username']));
        });

        $this->specify("username is too long", function() {
            $this->user->username = 'toolooooongnaaaaaaameeee';
            $this->assertFalse($this->user->validate(['username']));
        });

        $this->specify("username is ok", function() {
            $this->user->username = 'davert';
            $this->assertTrue($this->user->validate(['username']));
        });
    }
}

specifyのコードブロックを使用する事で、テストを細かい単位で説明することができます。このことはチームの全員にとってテストがとても見やすく、理解しやすい状態にしてくれます。

specifyブロックの内部にあるコードは分離されています。上記の例だと、$this->user(他のどんなオブジェクトやプロパティでも)への変更は他のコードブロックに反映されないでしょう。Specifyは分離されたスコープ間でオブジェクトを保存する際の手段として、ディープコピーとシャローコピーを使用します。ディープコピーを使用した場合はメモリ消費量の増加が、シャローコピーを利用した場合はスコープを完全に分離できない、という欠点があります。テストで使用する前に、Specifyがどのように動作するのか、そしてどのように設定を行うのか 理解してください。

あなたはBDD-styleのアサーションをするために、Codeception\Verifyも追加するかもしれません。もし、あなたがassertの呼び出しの中で、引数のどちらが期待している値で、どちらが実際の値なのかをよく混同してしまうなら、この小さなライブラリーはとてもすばらしく可読性に長けたアサーションを追加します。

<?php
verify($user->getName())->equals('john');

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
        - \Helper\Unit

Cest形式について もっと知る.

Cest形式は、テストを記述するにはシンプルすぎるように思えるかもしれません。 Cestはアサーション用の関数、モックやスタブを作成する関数、さらにはこれまで見てきた例にあったモジュールにアクセスするための`getModule`でさえも提供しません。 しかしながらCest形式は関心を分離するという点で優れています。テストコードは`UnitTester`オブジェクトによって提供されるサポートコードと干渉しません。単体/結合テストに必要となる、すべての追加アクションは`Helper\Unit`クラスに実装するようにしてください。

例外をチェックをするためにはAssertsモジュールのexpectExceptionメソッドを使うことができます。PHPUnitの同様のメソッドとは異なり、このメソッドはテスト内部でスローされた例外をアサートします。次のコードは例外の発生処理をクロージャーに内包しています。

<?php
$t->expectException(new Exception, {
   throw new Exception;
});

スタブ

Codeceptionは、スタブを簡単に作成するPHPUnitモックフレームワークの小さいラッパーを提供しています。\Codeception\Util\Stubを追加して、ダミーオブジェクトの作成を始めてください。

この例では、コンストラクタを呼び出さずにオブジェクトを初期化し、getNameメソッドがjohnという値を返すように置き換えています。

<?php
$user = Stub::make('User', ['getName' => 'john']);
$name = $user->getName(); // 'john'

スタブはPHPUnitのモックフレームワークから生成されます。MockeryMockery moduleとセット)、AspectMock、など他のものを代わりに使用することもできます。

スタブのユーティリティークラスの全リファレンスはここを見てください。

まとめ

テストスイートの中で、PHPUnitのテストはfirst-class citizensです。単体テストを書いて実行したいときはいつでも、PHPUnitをインストールする必要はなく、そのままCodeception上で実行することができます。いくつかのすばらしい特徴は、Codeceptionモジュールを統合する事で共通の単体テストを追加できることです。単体テストや結合テストのほとんどにおいて、PHPUnitのテストだけで十分です。PHPUnitのテストは速く、維持しやすいからです。