このエントリーは Symfony Advent Calendar 2015 18日目の記事です。
昨日は tarokamikaze さんの 「Symfony初心者がつまづきがちなポイント」 でした。
はじめに
CodeceptionはPHPのテスティング・フレームワークです。今回はこれをSymfonyプロジェクトに適用することを試してみました。
以前からCodeception
は気になっていたのですが、Using Codeception for Symfony Projects としてSymfony向けの記事が出ていたことから試してみようと思った次第です。
なお、手元で試したSymfonyのデモ・プロジェクトを以下のリポジトリに置いておきましたのでご参考まで。
- https://github.com/naoyes/2015-dec-quartetcom-tech-blog-codeception-symfony-demo-sample
また、当エントリはsymfonyコマンド
(Symfony Installer)および composer
の使用が前提になっています。
1. Symfonyプロジェクトを用意
今回はsymfony-demo
を利用することにします。
(ちなみにこのsymfony-demo
はブラウザで実際にアプリケーションを操作しつつ当該ページを生成するのにSymfonyではどのようなソースコードになっているのかを見ることができます。このソースコードはSymfony Best Pracitecesに準拠しているので安心して参考にできますね。)
$ symofny demo
$ cd symfony_demo
2. Codeceptionをインストール
まずはCodeceptionをSymfonyプロジェクトにインストールします。
$ composer require --dev "codeception/codeception:~2.1"
コマンド実行時に以下のようにエラーが出ることがあります。
$ composer require --dev "codeception/codeception:~2.1"
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.
Problem 1
- codeception/codeception 2.1.4 requires php >=5.4.0 -> your PHP version (5.5.20) or "config.platform.php" value does not satisfy that requirement.
- codeception/codeception 2.1.3 requires php >=5.4.0 -> your PHP version (5.5.20) or "config.platform.php" value does not satisfy that requirement.
- codeception/codeception 2.1.2 requires php >=5.4.0 -> your PHP version (5.5.20) or "config.platform.php" value does not satisfy that requirement.
- codeception/codeception 2.1.1 requires php >=5.4.0 -> your PHP version (5.5.20) or "config.platform.php" value does not satisfy that requirement.
- codeception/codeception 2.1.0 requires php >=5.4.0 -> your PHP version (5.5.20) or "config.platform.php" value does not satisfy that requirement.
- Installation request for codeception/codeception ~2.1 -> satisfiable by codeception/codeception[2.1.0, 2.1.1, 2.1.2, 2.1.3, 2.1.4].
Installation failed, reverting ./composer.json to its original content.
この際にはcomposer.json
を以下のように設定することでインストールできるようになりました。
# composer.json
"config": {
"bin-dir": "bin",
"platform": {
- "php": "5.3.9"
+ "php": "5.5.20"
}
},
3. テストの設置
ユニットテスト
環境準備
$ php bin/codecept bootstrap --empty src/AppBundle --namespace AppBundle
src/AppBundle
直下に設定ファイルcodeception.yml
が作成され、src/AppBundle/Tests
というディレクトリに必要なファイルが作成されました。
$ php bin/codecept generate:suite unit -c src/AppBundle
src/AppBundle
配下にユニットテストに必要なテストスイートが登録されました。
ただし、作成されたsrc/AppBundle/Tests/unit.suite.yml
を以下のように変更しないと次のステップには進めませんでした。
# src/AppBundle/Tests/unit.suite.yml
class_name: UnitTester
modules:
enabled:
- - \AppBundleHelper\Unit
+ - \AppBundle\Helper\Unit
次にアクタクラスというクラスを生成する必要があるようです。
$ php bin/codecept build -c src/AppBundle
Building Actor classes for suites: unit
-> UnitTesterActions.php generated successfully. 0 methods added
AppBundle\UnitTester includes modules: \AppBundle\Helper\Unit
UnitTester.php created.
テストの設置
ユニットテストの雛形を設置します。
$ php bin/codecept generate:phpunit unit Utils/SluggerTest -c src/AppBundle
その後テストを src/AppBundle/Tests/unit/Utils/SluggerTest.php
のように実装します。
<?php
// src/AppBundle/Tests/unit/Utils/SluggerTest.php
namespace AppBundle\Utils;
class SluggerTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getSlugs
*/
public function testSlugify($string, $slug)
{
$slugger = new Slugger();
$result = $slugger->slugify($string);
$this->assertEquals($slug, $result);
}
public function getSlugs()
{
return array(
array('Lorem Ipsum' , 'lorem-ipsum'),
array(' Lorem Ipsum ' , 'lorem-ipsum'),
array(' lOrEm iPsUm ' , 'lorem-ipsum'),
array('!Lorem Ipsum!' , 'lorem-ipsum'),
array('lorem-ipsum' , 'lorem-ipsum'),
);
}
}
その実symfony-demo
の提供しているテストsrc/AppBundle/Tests/Utils/SluggerTest.php
と記述方法としては変わりありません。
ユニットテストの記述方法にはCodeceptionの利用の有無は影響しませんね。
<?php
// src/AppBundle/Tests/Utils/SluggerTest.php
namespace Tests\Utils;
use AppBundle\Utils\Slugger;
class SluggerTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getSlugs
*/
public function testSlugify($string, $slug)
{
$slugger = new Slugger();
$result = $slugger->slugify($string);
$this->assertEquals($slug, $result);
}
public function getSlugs()
{
return array(
array('Lorem Ipsum' , 'lorem-ipsum'),
array(' Lorem Ipsum ' , 'lorem-ipsum'),
array(' lOrEm iPsUm ' , 'lorem-ipsum'),
array('!Lorem Ipsum!' , 'lorem-ipsum'),
array('lorem-ipsum' , 'lorem-ipsum'),
);
}
}
テストの実行
以下のように無事にテストが実行されました。
$ php bin/codecept run unit -c src/AppBundle
Codeception PHP Testing Framework v2.1.4
Powered by PHPUnit 4.8.21 by Sebastian Bergmann and contributors.
AppBundle.unit Tests (1) -----------------------------------------------------------------------------------
Utils\SluggerTest::testSlugify | #0 Ok
Utils\SluggerTest::testSlugify | #1 Ok
Utils\SluggerTest::testSlugify | #2 Ok
Utils\SluggerTest::testSlugify | #3 Ok
Utils\SluggerTest::testSlugify | #4 Ok
------------------------------------------------------------------------------------------------------------
Time: 1.54 seconds, Memory: 10.25Mb
OK (5 tests, 5 assertions)
機能テスト
環境準備
$ php bin/codecept generate:suite functional -c src/AppBundle
src/AppBundle
配下に機能テストに必要なテストスイートが登録されました。
ただし、作成されたsrc/AppBundle/Tests/functional.suite.yml
を以下のように変更しないと次のステップには進めませんでした。
# src/AppBundle/Tests/functional.suite.yml
class_name: FunctionalTester
modules:
enabled:
- - \AppBundleHelper\Functional
+ - \AppBundle\Helper\Functional
ここでもアクタクラスというクラスを生成します。
$ php bin/codecept build -c src/AppBundle
Building Actor classes for suites: functional, unit
-> FunctionalTesterActions.php generated successfully. 0 methods added
AppBundle\FunctionalTester includes modules: \AppBundle\Helper\Functional
FunctionalTester.php created.
-> UnitTesterActions.php generated successfully. 0 methods added
AppBundle\UnitTester includes modules: \AppBundle\Helper\Unit
テストの設置
機能テストの雛形を設置します。
$ php bin/codecept generate:cest functional BlogCest -c src/AppBundle
その後テストを src/AppBundle/Tests/functional/BlogCest.php
のように実装します。
<?php
// src/AppBundle/Tests/functional/BlogCest.php
namespace AppBundle;
use AppBundle\FunctionalTester;
use AppBundle\Entity\Post;
class BlogCest
{
public function postsOnIndexPage(FunctionalTester $I)
{
$I->amOnPage('/en/blog/');
$I->seeNumberOfElements('article.post', Post::NUM_ITEMS);
}
}
symfony-demo
の提供しているテストsrc/AppBundle/Tests/Controller/BlogControllerTest.php
に比べて短くて直感的ですね。
<?php
// src/AppBundle/Tests/Controller/BlogControllerTest.php
namespace AppBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use AppBundle\Entity\Post;
class BlogControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient();
$crawler = $client->request('GET', '/en/blog/');
$this->assertCount(
Post::NUM_ITEMS,
$crawler->filter('article.post'),
'The homepage displays the right number of posts.'
);
}
}
テストの実行
ではテストを実行してみます。
$ php bin/codecept run functional -c src/AppBundle
Codeception PHP Testing Framework v2.1.4
Powered by PHPUnit 4.8.21 by Sebastian Bergmann and contributors.
AppBundle.functional Tests (1) ------------------------------------------------------------------------------------------------------------------------------
Posts on index page (BlogCest::postsOnIndexPage) Error
-------------------------------------------------------------------------------------------------------------------------------------------------------------
Time: 1.41 seconds, Memory: 10.75Mb
There was 1 error:
---------
1) Failed to posts on index page in AppBundle\BlogCest::postsOnIndexPage (tests/functional/BlogCest.php)
[RuntimeException] Call to undefined method AppBundle\FunctionalTester::amOnPage
#1 /path/to/symfony_demo/src/AppBundle/tests/functional/BlogCest.php:11
#2 /path/to/symfony_demo/src/AppBundle/tests/functional/BlogCest.php:11
#3 AppBundle\BlogCest->postsOnIndexPage
FAILURES!
Tests: 1, Assertions: 0, Errors: 1.
エラーになりました。これはテスト対象の機能が未実装なのではなく設定が足りないためです。
機能テストが正しく実行されるようにsrc/AppBundle/Tests/functional.suite.yml
にSymfony2
およびDoctrine
モジュールが有効になるように設定してやる必要があります。
# src/AppBundle/Tests/functional.suite.yml
class_name: FunctionalTester
modules:
enabled:
+ - Symfony2:
+ app_path: '../../app'
+ var_path: '../../app'
+ - Doctrine2:
+ depends: Symfony2
- \AppBundle\Helper\Functional
再度トライしてみます。
$ php bin/codecept run functional -c src/AppBundle
Codeception PHP Testing Framework v2.1.4
Powered by PHPUnit 4.8.21 by Sebastian Bergmann and contributors.
AppBundle.functional Tests (1) ------------------------------------------------------------------------------------------------------------------------------
Posts on index page (BlogCest::postsOnIndexPage) Ok
-------------------------------------------------------------------------------------------------------------------------------------------------------------
Time: 651 ms, Memory: 40.50Mb
OK (1 test, 1 assertion)
これで無事にテストが実行されました。
受入テスト
環境準備
$ php bin/codecept bootstrap --empty
プロジェクトルート直下にtests
というディレクトリと設定ファイルcodeception.yml
が作成されました。このtests
に受入テストのコードを配置することにします。
$ php bin/codecept generate:suite acceptance -c ./
プロジェクトルート直下に受入テストに必要なテストスイートが登録されました。
次に、ここでもアクタクラスというクラスを生成します。
$ php bin/codecept build -c ./
Building Actor classes for suites: acceptance
-> AcceptanceTesterActions.php generated successfully. 0 methods added
\AcceptanceTester includes modules: \Helper\Acceptance
AcceptanceTester.php created.
テストの設置
受入テストの雛形を設置します。
$ php bin/codecept generate:cept acceptance BlogCept -c ./
その後テストを tests/acceptance/BlogCept.php
のように実装します。これも機能テストと同様の記述方法ですね。
<?php
// tests/acceptance/BlogCept.php
$I = new AcceptanceTester($scenario);
$I->wantTo('open blog page and see article there');
$I->amOnPage('/');
$I->click('Browse application');
$I->seeInCurrentUrl('blog');
$I->seeElement('article.post');
テストの実行
走らせてみます。
$ php bin/codecept run acceptance -c ./
Codeception PHP Testing Framework v2.1.4
Powered by PHPUnit 4.8.21 by Sebastian Bergmann and contributors.
Acceptance Tests (1) -------------------------------------------------------------------------------------------------------------------------------------------
Open blog page and see article there (BlogCept) Error
----------------------------------------------------------------------------------------------------------------------------------------------------------------
Time: 1.03 seconds, Memory: 10.50Mb
There was 1 error:
---------
1) Failed to open blog page and see article there in BlogCept (tests/acceptance/BlogCept.php)
[RuntimeException] Call to undefined method AcceptanceTester::amOnPage
#1 /path/to/symfony_demo/tests/acceptance/BlogCept.php:4
#2 /path/to/symfony_demo/tests/acceptance/BlogCept.php:4
FAILURES!
Tests: 1, Assertions: 0, Errors: 1.
エラーになりました。tests/acceptance.suite.yml
を以下のように変更します。
# tests/acceptance.suite.yml
class_name: AcceptanceTester
modules:
enabled:
+ - PhpBrowser:
+ url: 'http://127.0.0.1:8000'
+ headers:
+ env: test
- \Helper\Acceptance
これで無事にテストが実行されました。
$ php bin/codecept run acceptance -c ./
Codeception PHP Testing Framework v2.1.4
Powered by PHPUnit 4.8.21 by Sebastian Bergmann and contributors.
Acceptance Tests (1) ----------------------------------------------------------------------------------------------------------------------
Open blog page and see article there (BlogCept) Ok
-------------------------------------------------------------------------------------------------------------------------------------------
Time: 1.01 seconds, Memory: 14.00Mb
OK (1 test, 2 assertions)
すべての種類のテストを一度に実行する
codeception.yml
にユニットテストおよび機能テストのソースコードの場所を規定してやります。
# codeception.yml
actor: Tester
+include:
+ - src/*Bundle
paths:
tests: tests
log: tests/_output
これで無事に設置してあるすべてのテストが実行されました。
$ php bin/codecept run
Codeception PHP Testing Framework v2.1.4
Powered by PHPUnit 4.8.21 by Sebastian Bergmann and contributors.
Acceptance Tests (1) ----------------------------------------------------------------------------------------------------------------------
Open blog page and see article there (BlogCept) Ok
-------------------------------------------------------------------------------------------------------------------------------------------
[AppBundle]: tests from
/path/to/symfony_demo/src/AppBundle
AppBundle.functional Tests (1) ------------------------------------------------------------------------------------------------------------------------------
Posts on index page (BlogCest::postsOnIndexPage) Ok
-------------------------------------------------------------------------------------------------------------------------------------------------------------
AppBundle.unit Tests (1) -----------------------------------------------------------------------------------
Utils\SluggerTest::testSlugify | #0 Ok
Utils\SluggerTest::testSlugify | #1 Ok
Utils\SluggerTest::testSlugify | #2 Ok
Utils\SluggerTest::testSlugify | #3 Ok
Utils\SluggerTest::testSlugify | #4 Ok
------------------------------------------------------------------------------------------------------------
Time: 865 ms, Memory: 42.50Mb
OK (7 tests, 8 assertions)
まとめ
とりあえずは一通りのテストを書いて実行するところまではできました。なお、Symfony3においてはディレクトリ構造が変更になっているため当エントリのままでは動かないと思います。
現時点ではCodeceptionの詳細な動作の把握や、明確なメリットの享受には至っていません。しかし、機能テストおよび受入テストのヒューマンフレンドリな記述方法は魅力に感じました。
みなさんのテスト環境選択の材料のひとつとして当エントリがわずかばかりでもお役に立てれば幸いです。
明日は @brtriver さんです!