slimでphpunit使用してユニットテスト
今回はphpunitをインストールした上で、ユニットテストを行う手順を記します。
ユニットテストはメソッド単体だったりの小さい粒度のテストで
今回はsrc/Taka512/Services/NameService.phpのgetNameメソッドをテストします。
サービスクラスは以下のような感じでコンテナに登録しているとします。
$container['app.services.name_service'] = $container->share(function ($c) { return new \Taka512\Services\NameService($c); });
まずアプリケーション側をテストしやすい形に作り替えます。
具体的にはindex.phpで行っていたコンテナ作成処理等をbootstrap.phpに移動します。
web/index.php
<?php define('ENVIRONMENT', 'prod'); require '../config/bootstrap.php'; $container = createContainer(); $app = $container['app']; require PROJECT_DIR.'/config/routes.php'; $app->run();
config/bootstrap.php
<?php define('PROJECT_DIR', dirname(__FILE__) . '/..'); require PROJECT_DIR . '/vendor/autoload.php'; function createContainer() { require PROJECT_DIR . '/config/config.php'; $container = new \Pimple(); $container['app'] = new \Slim\Slim($config); require PROJECT_DIR . '/config/parameters.php'; require PROJECT_DIR . '/config/databases.php'; require PROJECT_DIR . '/config/services.php'; return $container; }
phpunitのインストール
composerでインストールするとvendor/binの下にphpunitがインストールされます。
$ vi composer.json "require": { "slim/slim": "2.*", "slim/extras": "2.0.*", "twig/twig": "1.*", "pimple/pimple": "v1.0.2", "phpunit/phpunit": "3.7.21", }, $ php composer.phar update $ ls vendor/bin/phpunit vendor/bin/phpunit
テストの設定ファイルを作る
ユニットテスト用の設定ファイルをconfigの下に作成します。
先ほど作成したbootstrapを読み込んで「tests/unit」下のテストを起動する設定となります。
$ vi config/unit.xml <?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap = "bootstrap.php" > <testsuites> <testsuite name="unit test"> <directory>../tests/unit</directory> </testsuite> </testsuites> </phpunit>
テスト作成
テストディレクトリを掘ってテストファイルを作成します。
$ mkdir -p test/unit/Services $ vi tests/unit/Services/NameServiceTest.php <?php namespace Taka512\Services; class NameServiceTest extends \PHPUnit_Framework_TestCase { public function testGetName() { \Slim\Environment::mock(); $container = createContainer(); $name = $container['app.services.name_service']->getName(); $this->assertEquals($name, 'hoge'); } }
テスト実行
設定ファイルを指定してphpunitを実行するとサービスクラスのテストができましたとさ!
$ vendor/bin/phpunit -c config/unit.xml PHPUnit 3.7.21 by Sebastian Bergmann. Configuration read from /home/coh2/config/unit.xml . Time: 0 seconds, Memory: 5.00Mb OK (1 test, 1 assertion)
slimでpimpleを使ってデータベースに接続
pimpleを使ってデータベースへの接続を行いデータベースからデータを取ってくるようにします。
データベースにテーブルとデータを追加
MYSQLのhogeデータベースにユーザ「hoge」パスワード「hogepass」で接続できるように設定してます。
create databases hoge; GRANT ALL PRIVILEGES ON hoge.* TO hoge@localhost IDENTIFIED BY 'hogepass'; flush privileges; mysql> use hoge mysql> create table user (name varchar(10)); mysql> insert into user (name)values('taka512');
dbに接続するクラスを作成
PDOを利用してMYSQLに接続するクラスです。
$ mkdir src/Taka512/Db $ vi src/Taka512/Db/Mysql.php <?php namespace Taka512\Db; class Mysql { protected static $conn = null; public static function connect($host, $database, $user, $password) { if (self::$conn == null) { try { self::$conn = new \PDO( sprintf('mysql:host=%s;dbname=%s;charset=utf8', $host, $database), $user, $password, array(\PDO::ATTR_EMULATE_PREPARES => false) ); } catch (\PDOException $e) { echo 'Connection failed: ' . $e->getMessage(); die; } } return self::$conn; } }
dbからデータを取得処理を作成
今までgetNameは固定文字列を返してましたがデータベースからデータを取得するように修正
$ vi src/Taka512/Services/NameService.php class NameService { ~省略~ public function getName() { $name = null; try { $stmt = $this->di['db_master']->query("SELECT name FROM user"); while($row = $stmt->fetch(\PDO::FETCH_ASSOC)){ $name = $row['name']; } } catch (\PDOException $e){ var_dump($e->getMessage()); die; } return $name;
index.phpの修正
pimpleにデータベースへの接続処理を追加します。
$ vi web/index.php ~省略~ $container = new Pimple(); $container['app'] = $app; $container['db_master'] = $container->share(function($c){ return \Taka512\Db\Mysql::connect( 'localhost'/*host*/, 'hoge'/*database*/, 'hoge'/*id*/, 'hogepass'/*pass*/); });
こんな感じでデータベースへの接続処理を追加できます。
slimでpimpleを使ってroutingを行う
pimpleを使ってslimのroutingを構造化してみます。
routes.phpの作成
configディレクトリを作成し、routing情報を記述するroutes.phpを作成します。
$ mkdir config $ vi config/routes.php <?php $app->get('/', function() use ($container) { $container['ContentsController']->top(); });
ContentsControllerクラスの作成
index.phpのcontrollerの中に記述していた処理をContentsControllerクラスとして独立させます。
topメソッドの中にcontroller処理を記述してます。
$ mkdir src/Taka512/Controllers $ vi src/Taka512/Controllers/ContentsController.php <?php namespace Taka512\Controllers; class ContentsController { protected $app; protected $service; public function __construct(\Pimple $di) { $this->app = $di['app']; $this->init($di); } public function init(\Pimple $di) { $this->service = $di['NameService']; } public function top() { $name = $this->service->getName(); $this->app->render('index.html.twig', array('name' => $name)); } }
index.phpを修正
controller処理を削除してroutes.phpの読み込み処理を追加します。
$ vi web/index.php 9 define('PROJECT_DIR', dirname(__FILE__) . '/..'); 10 $app = new Slim(array( ~略~ 20 $container = new Pimple(); 21 $container['app'] = $app; 22 $container['NameService'] = $container->share(function ($container) { 23 return new \Taka512\Services\NameService($container); 24 }); 25 26 $container['ContentsController'] = $container->share(function ($container) { 27 return new \Taka512\Controllers\ContentsController($container); 28 }); 29 30 require PROJECT_DIR.'/config/routes.php'; 31 32 $app->run();
これでroutes.phpにrouting情報を定義してControllerクラスをモリモリ書いていけるようになりました。
今回の作業をした後のディレクトリ構造は下記となります。
|-- composer.json |-- composer.lock |-- logs | |-- 2013-06-29.log |-- templates | |-- index.html.twig |-- config | |-- routes.php |-- vendor |-- web | |-- index.php |-- src | |-- Taka512 | |-- Controllers | |-- ContentsController.php | |-- Services | |-- NameService.php
参考
http://nesbot.com/2012/11/5/lazy-loading-slim-controllers-using-pimple
slimでネームスペースを利用するように修正
プロジェクトディレクトリの構造化を行っていきます。
今回は前回index.phpに作成したNameServiceクラスをファイルから分離して、ネームスペースを利用するように修正します。
composer.jsonの修正
autoloadの項目を追加します。srcが設定ディレクトリで「Taka512」ネームスペースとなります。
$ vi composer.json "require": { "slim/slim": "2.*", "slim/extras": "2.0.*", "twig/twig": "1.*", "pimple/pimple": "1.0.0" }, "autoload": { "psr-0": { "Taka512": "src/"} } $ php composer.phar update
ディレクトリの作成
先ほどautoloadで設定したディレクトリを作成します。
$ mkdir -p src/Taka512/Services
NameServiceライブラリの作成
NameServiceクラスをファイルに分離します。今回の例ではネームスペースは「Taka512\Services」となります。
constructのPimpleの前に「\」マークをつけるのを忘れないようにしましょう。つけないと「Taka512\Service\Pimple」を探しに行ってエラーとなります。
$ vi src/Taka512/Services/NameService.php <?php namespace Taka512\Services; class NameService { protected $di; public function __construct(\Pimple $di) { $this->di = $di; } public function getName() { return 'taka512'; } }
index.phpの修正
index.phpからNameServiceクラスを削除して、サービスの定義の部分のネームスペースを変更します。
$container['NameService'] = $container->share(function ($container) { return new \Taka512\Services\NameService($container); });
これでネームスペースを利用してライブラリをもりもり作っていけるようになりました。
slimでpimpleを使う
pimpleはphpでDIコンテナを実現するためのライブラリです。
今回はslimでpimpleを使ってみます。
インストール
composer.jsonにpimpleを追加
$ vi composer.json "require": { "slim/slim": "2.*", "slim/extras": "2.0.*", "twig/twig": "1.*", "pimple/pimple": "1.0.0" $ php composer.phar update
index.phpを編集
32~45行目でNameServiceを定義
19~23行目でNameServiceをpimpleに登録
26行目でNameServiceをpimpleから利用してます。
$ vi web/index.php 10 $app = new Slim(array( ~略~ 19 $container = new Pimple(); 20 $container['app'] = $app; 21 $container['NameService'] = $container->share(function ($container) { 22 return new NameService($container); 23 }); 25 $app->get('/', function() use ($container) { 26 $name = $container['NameService']->getName(); 27 $container['app']->render('index.html.twig', array('name' => $name)); 28 }); 29 30 $app->run(); 32 33 class NameService 34 { 35 protected $di; 36 public function __construct(Pimple $di) 37 { 38 $this->di = $di; 39 } 41 public function getName() 42 { 43 return 'taka512'; 44 } 45 }
pimpleの何が嬉しいかというとLazyロードなのでNameServiceは26行目で実際に使われるタイミングで生成されます。
slimでログを出力
前回インストールしたslim/extrasを利用してログ出力を行う設定のメモ
ログディレクトリを作成
$ mkdir -p logs $ chmod 777 logs
index.phpを編集
$app = new Slim(array( 'view' => new Twig, 'templates.path' => '../templates', 'log.writer' => new \Slim\Extras\Log\DateTimeFileWriter(array( 'path' => '../logs', 'name_format' => 'Y-m-d', 'message_format' => '%label% - %date% - %message%' )) )); $app->get('/', function() use ($app) { $app->getLog()->debug('log'); // - level 4 $app->getLog()->info('log'); // - level 3 $app->getLog()->warn('log'); // - level 2 $app->getLog()->error('log'); // - level 1 $app->getLog()->fatal('log'); // - level 0 $app->render('index.html.twig', array('name' => 'taka512')); });
index.phpにアクセスした後にログを見てみると出力されていることが確認できます。
$ tail -f logs/2013-06-29.log DEBUG - 2013-06-29T16:56:47+09:00 - log INFO - 2013-06-29T16:56:47+09:00 - log WARN - 2013-06-29T16:56:47+09:00 - log ERROR - 2013-06-29T16:56:47+09:00 - log FATAL - 2013-06-29T16:56:47+09:00 - log
今回の作業をした後のディレクトリ構造はこんな感じとなります。
|-- composer.json |-- composer.lock |-- logs | |-- 2013-06-29.log |-- templates | |-- index.html.twig |-- vendor |-- web |-- index.php
slimでtwig連携
最近slimを使う機会がありそうなのでphpのマイクロフレームワークであるslimを基礎から触った。
まずはインストールしてslimとtwigで連携するところまで行う。
composerからのslimのインストール
$ curl -s http://getcomposer.org/installer | php $ vi composer.json { "require": { "slim/slim": "2.*", "slim/extras": "2.0.*", "twig/twig": "1.*" } } $ php composer.phar install
index.phpの作成
$ mkdir web $ vi web/index.php <?php require '../vendor/autoload.php'; use Slim\Slim; use Slim\Extras\Views\Twig as Twig; $app = new Slim(array( 'view' => new Twig, 'templates.path' => '../templates' )); $app->get('/', function() use ($app) { $app->render('index.html.twig', array('name' => 'taka512')); }); $app->run();
templateを作成
$ mkdir templates $ vi templates/index.html.twig hello {{ name }}
これでindex.phpにアクセスするとテンプレートの内容が表示されます。
そして今回のディレクトリ構造はこんな感じとなります。
|-- composer.json |-- composer.lock |-- templates | |-- index.html.twig |-- vendor |-- web |-- index.php