「スピード」と「品質」を実現する 
PHP開発チームの取り組み 
! 
 ~AngularJS + FuelPHP + AspectMock~
何者? 
株式会社インテリジェンス 
 マーケティング企画統括部 
 サービス開発部 テクノロジーグループ 
! 
清田 馨一郎  
Twitter:@seikei1874 
【経歴】 
2002年 SIerに入社 
PGから叩き上げでPMまで経験 
大手企業の基幹システムからソーシャルゲーム開発まで幅広く経験 
  
2014年04月から、インテリジェンスへJOIN 
石抱き
何してる? 
サービス開発部 
 dots.(http://eventdots.jp/)の開発、運営 
 マーケティング部門の業務改善、見える化 
 … etc 
! 
(私の)ミッションを格好良く言うと 
 社内のデータサイロを見つけ、 
 サイロをつなぐデータパイプラインを構築し、 
 ビジネスの加速を促す
本日の内容
本日の内容 
【「スピード」と「品質」を実現する】取り組みを紹介 
その中でも、テストを中心に紹介 
PHP勉強会なので、AspectMockについて詳しく紹介 
Angularの技術的な話は。。。 
! 
ゴール 
 明日から、AspectMockが使いたくなっちゃう♥
スピードと品質
スピードと品質 
計画(スプリント・プランニング) 
実装 
ユーザに見てもらう(スプリント・レビュー)
スピードと品質 
計画(スプリント・プランニング) 
サイクルを早く回す 
実装 
ユーザに見てもらう(スプリント・レビュー)
サイクルを早く回すために 
テストは極力自動化 
定常的、同じ作業は機械に任せる 
! 
細かいスパンでデプロイ 
ユーザは、見て・使ってみないと分からない 
頻繁なデプロイが負荷にならないために自動化 
! 
技術的負債は残さない 
開発者の精神的安定
開発
開発システム 
PHP :5.5系 
MySQL :5.6系 
FuelPHP :1.7.2 
 ※ 一部システムでは、Phalcon使ったり 
! 
Font-end :AngularJS、TypeScript 
Test :PHPUnit、AspectMock、Karma、Jasmine、PhantomJS 
コミュニケーション :Slack 
ジョブ管理 :SOS JobScheduler 
ビルド、デプロイ :Grunt、Fabric 
CI :Jenkins 
構成管理 :Gitlab 
開発環境 :Vagrant 
課題管理 :OpenProject
開発フローとCI 
開発フロー 
 テストコードを書いて実装完了 
 Gitlabにマージリクエスト。レビューしてマージ 
 Jenkinsでテスト、デプロイ 
 Slackに通知して共有 
! 
CI 
 毎朝、実行 
 Slackに結果を通知
フロントサイド
FuelPHP 
FulePHPでは極力APIを作るようにする 
ビュー部分はAngularJSで作りこむ 
! 
素のController_Restでは、 
想定外エラーのレスポンスなど 
扱いヅライのでカスタマイズ
AngularJS 
多分に漏れず、DOM地獄から逃げたかった 
社内ツールではあるが、UIは今風にしたい 
! 
秘伝のjQueryソースは無くしたい 
ある程度、書き方が統一できる
TypeScript 
コンパイルを通すので、構文チェックができる。 
デバッグでエラー箇所が特定し易い 
→ 作業効率は上がる
Wijmo(ウィジモ) 
リッチUIを提供するJavaScriptライブラリ 
データグリッドやチャートなどのWidgetを多数提供 
AngularのDirectiveが標準で提供 
!
Grunt + Karma + Jasmine + PhantomJS 
Grunt 
 TypeScriptのコンパイル  
 Karmaの実行 
! 
 デプロイ、CI 
  JenkinsでGrunt実行 
! 
Jasmine 
APIレスポンスをスタブ化し、 
テストが柔軟にできる
AspectMock
AspectMock 
モック フレームワーク 
https://github.com/Codeception/AspectMock 
! 
PHPテストフレームワーク「Codeception」と同じ作者 
! 
PHPでAOPを実現する「Go-AOP」を使用して 
メソッドを差し替える仕組み 
! 
Go-AOP 
https://github.com/lisachenko/go-aop-php 
AOP:アスペクト志向プログラミング
なぜAspectMock? 
テストフルなコード??? 
! 
FuelPHPとの親和性 
Fuelは、staticを多様 
1.7.2から標準で設定済(core/bootstrap_phpunit.php) 
! 
単体テストのバリエーションが増える  
テストデータ作成に苦労しない 
異常ケースが容易にできる
設定、使い方 
達人出版会 
「はじめてのフレームワークとし 
てのFuelPHP第2版(3) 実践編」 
! 
※AspectMock使用時のクラスロードエラーの解消の部分だけでも、1000円の価値はありました。 
○ PHP Advent Calendar 2014 kenjisさんの記事 
「普通じゃないモッキングフレームワークAspectMockがパワフル過ぎる」 
  http://blog.a-way-out.net/blog/2014/12/10/aspect-mock/
Proxy 
ClassProxy 
静的メソッドのMock 
! 
InstansProxy 
インスタンスのMock 
! 
Test Doubles Builder 
ClassProxy, InstansProxyを良しなに作成してくれる 
! 
FuncProxy (>= 5.0.0) 
指定したNamespaceのファンクションをMock化。 
NativeファンクションもMock化可能!!
こんなときどうする? 
1. オブジェクトの中で呼んでいるstaticメソッド 
2. DBエラー、ネットワークエラーなどの例外 
3. 外部リソースからの取得データ 
4. 状態によって戻り値が変わるメソッド 
DEMOしながら説明します
まとめ 
• 定常、定期的な作業は積極的に自動化すべし 
• テストコードは、テスト実施と同時に書く 
• AspectMockを使えば、出来ないテストは無い(多分) 
• SpecメソッドでBDDも可能(試してません) 
• AspectMockのクセは強いので慣れましょう
ご質問ありますか? 
http://eventdots.jp/
ありがとうございました! 
http://eventdots.jp/ 
いっしょに働く仲間を募集中!!!
付録
FuelPHP 
想定外のエラー発生時も、ちゃんとレスポンスを返すようにする 
! 
 protected function response($data = array(), $message = '') {! 
! if(!array_key_exists('error_code', $data)) {! 
! ! $m_array = Arr::merge(array('error_code' => '200', 'message' => $message), array('data' => $data));! 
! }! 
! parent::response($m_array);! 
 }! 
! 
 public function router($resource, $arguments) {! 
! try {! 
$ret = parent::router($resource, $arguments);! ! 
if ($ret === false) {! 
parent::response(array('error_code' => '403', 'message' => 'Exception'), 403);! 
}! 
} catch (Exception $e) {! 
Log::error($e->getTraceAsString());! 
Log::error($e->getMessage());! ! 
parent::response(array('error_code' => '500', 'message' => 'Internal error.'), 500);! 
}! 
}! 
 }!
AspectMockを使ってみる 
こんなクラスをMock化してみる 
<?php! 
! 
namespace Sample;! 
! 
class Model_User extends Model {! 
! 
private $_id;! 
private $_name;! 
! 
public function __construct($id, $name) {! 
$this->_id = $id;! 
$this->_name = $name;! 
}! 
! 
public function getName() {! 
return $this->_name;! 
}! 
! 
public function getDate() {! 
return date('Y-m-d H:i:s');! 
}! 
} 
<?php! 
! 
namespace Sample;! 
! 
class Model_Suser extends Model {! 
! 
private static $_name = '静的な値';! 
! 
public static function getName() {! 
return static::$_name;! 
}! 
! 
public static function callPrivate() {! 
return static::privatefunc();! 
}! 
! 
private static function privatefunc() {! 
$time = FuelCoreDate::time();! 
return "private:" . $time . PHP_EOL;! 
}! 
}
AspectMockを使ってみる1 
<?php! 
use AspectMockTest as mock;! 
! 
class Test_Sample extends FuelCoreTestCase {! 
! 
protected function setUp() {! 
Autoloader::add_namespace(! 
! ! ! ‘Sample',! 
! ! ! APPPATH . 'classes' . DS . 'sample/');! 
}! 
! 
public function test_インスタンスProxyのケース() {! 
$user = new SampleModel_User(1, 'TestName');! 
! 
// Mock化! 
$mock = mock::double($user,! 
! ! ! ['getName' => 'DummyName']);! 
! 
// 指定したNamespace内であれば、標準関数もMock化できる! 
mock::func('Sample', 'date', ‘now!!');! 
! 
$name = $user->getName();! 
$data = $user->getDate();! 
! 
$mock->verifyInvokedOnce('getName');! 
$mock->verifyInvokedOnce('getDate');! 
! 
$this->assertEquals('DummyName', $name);! 
$this->assertEquals('now!!', $data);! 
}! 
} 
public function test_ClassProxyのケース() ! 
{! 
! 
mock::double('SampleModel_Suser', [! 
! ! ! ! ! 'getName' => 'Dummy']);! 
! 
$name = SampleModel_Suser::getName();! 
! 
$this->assertEquals('Dummy', $name);! 
! 
} 
インスタンスもMock化 
クラス名からMock化 
標準関数もMock化!
AspectMockを使ってみる2 
/**! 
* @expectedException FuelException! 
*/! 
public function test_例外発生ケース() {! 
例外を強制的に発生できる 
DBエラー、ネットワークエラーなど、発生させ難いテストが容易になる 
! 
mock::double('SampleModel_Suser', [! 
'getName' => function() {throw new FuelCoreFuelException("例外発生");}! 
]);! 
! 
try {! 
SampleModel_Suser::getName();! 
! 
} catch (FuelException $e) {! 
! 
$this->assertTrue(true);! 
throw $e;! 
}! 
! 
$this->assertTrue(false);! 
}!
AspectMockを使ってみる3 
public function test_Privateなメソッドのケース() {! 
privateメソッドもMock化できる 
 ※AspectMockの機能ではありませんが、Closureでprivateメソッドが直接呼べる 
! 
// privateメソッドで呼んでるクラスをMock化! 
mock::double('FuelCoreDate', ['time' => 'hogehoge']);! 
! 
// Privateメソッドを直接呼ぶ! 
Closure::bind(! 
function () {! 
$obj = new SampleModel_Suser();! 
$ret = $obj->privatefunc();! 
! 
$this->assertEquals('private:hogehoge' . PHP_EOL, $ret);! 
},! 
$this,! 
'SampleModel_Suser'! 
)->__invoke();! 
! 
mock::clean();! 
mock::double('SampleModel_Suser', ['privatefunc' => 'プライベート関数もMock化']);! 
! 
$ret = SampleModel_Suser::callPrivate();! 
$this->assertEquals('プライベート関数もMock化', $ret);! 
! 
}
AspectMockを使ってみる4 
<?php! 
namespace Sample;! 
! 
use FuelCoreDB;! 
! 
class Model_Transaction extends Model {! 
! 
public static function transaction() {! 
! 
if(!DB::in_transaction()) {! 
DB::start_transaction();! 
}! 
! 
try {! 
// DB処理! 
Model_Orm_User::find();! 
! 
DB::commit_transaction();! 
! 
} catch(FuelException $e) {! 
if(DB::in_transaction()) {! 
DB::rollback_transaction();! 
}! 
! 
throw $e;! 
}! 
}! 
}! 
【ケース】 
 例外が発生したときの挙動をテストしたい 
  
 1. トランザクションが無ければ貼る 
 2. 例外が発生しとき、トランザクションが 
   貼ってあれば、Rollbackする
AspectMockを使ってみる4 
public function test_呼び出し回数で挙動を変えるケース() ! 
{! 
! $cnt = 0;! 
! $mock = mock::double(! 
'FuelCoreDB',! 
[! 
'start_transaction' => true,! 
'commit_transaction' => true,! 
'rollback_transaction' => true,! 
'in_transaction' => function () use (&$cnt) {! 
if ($cnt == 0) {! 
$cnt++;! 
return false;! 
} elseif ($cnt == 1) {! 
$cnt++;! 
return true;! 
} else {! 
$cnt++;! 
return __AM_CONTINUE__; // オリジナルの処理がされる! 
}! 
}]);! 
! 
mock::double('SampleModel_Orm_User', [! 
! ! ! 'find' => function() {! 
! ! ! ! throw new FuelException(“Exception強制発生");}! 
! ! ]);! 
! 
try {! 
SampleModel_Transaction::transaction();! 
} catch(FuelException $e) {! 
$mock->verifyInvokedOnce('start_transaction');! 
$mock->verifyNeverInvoked('commit_transaction');! 
$mock->verifyInvokedMultipleTimes('in_transaction', 2);! 
$mock->verifyInvokedOnce('rollback_transaction');! 
}! 
} 
無名関数の引数に 
変数を参照渡しして 
呼び出し回数を 
カウント
AspectMockを使ってみる5 
【ケース】 
ORMでfindした値が、 
想定どおりの処理が行われた値で 
saveされるかを確認したい 
<?php! 
! 
namespace Sample;! 
! 
class Model_Orm_User extends OrmModel! 
{! 
! 
<?php! 
! 
namespace Sample;! 
! 
class Model_Update extends Model! 
{! 
public static function update()! 
{! 
$model = Model_Orm_User::find();! 
! 
foreach($model as $ret) {! 
$ret['val'] = 'hugehuge';! 
$ret->save();! 
}! 
}! 
}! 
protected static $_properties = [! 
'id',! 
'val',! 
];! 
}!
AspectMockを使ってみる5 
確認したいORMクラ 
スを継承してfindし 
た結果をダミー値で 
定義。 
! 
saveの引数を無名関 
数でチェック 
<?php! 
Autoloader::add_namespace(‘Sample',! 
! ! ! ! APPPATH . 'classes' . DS . 'sample/');! ! 
class Tests_Model_StubModel extends SampleModel_Orm_User {! ! 
protected $_data = [‘id','val'];! ! 
function __construct($id, $val){! 
$this->_data['id'] = $id;! 
$this->_data['val'] = $val;! 
}! 
} 
public function test_ORMでの更新値チェックのケース() {! 
// ORMでDBから値を取得して処理して更新するパターン! 
$modify_data = 'hugehuge';! ! 
$data = new Tests_Model_StubModel('1', 'hogehoge');! ! 
mock::double('SampleModel_Orm_User',! 
[! 
'find' => function ($param) use ($data) {! 
// ORMのfindは、レコード単位のORMクラス配列が返る! 
return [$data];! 
}! 
]);! ! 
mock::double('OrmModel',[! 
'save' => function() use ($modify_data) {! 
FuelCoreTestCase::assertEquals(! 
! ! ! $this->_data['val'], $modify_data! 
! ! );! 
}! 
]);! ! 
SampleModel_Update::update();! 
}!

「スピード」と「品質」を実現するPHP開発チームの取り組み~AngularJS+FuelPHP+AspectMock~

  • 1.
  • 2.
    何者? 株式会社インテリジェンス  マーケティング企画統括部  サービス開発部 テクノロジーグループ ! 清田 馨一郎  Twitter:@seikei1874 【経歴】 2002年 SIerに入社 PGから叩き上げでPMまで経験 大手企業の基幹システムからソーシャルゲーム開発まで幅広く経験   2014年04月から、インテリジェンスへJOIN 石抱き
  • 3.
    何してる? サービス開発部  dots.(http://eventdots.jp/)の開発、運営  マーケティング部門の業務改善、見える化  … etc ! (私の)ミッションを格好良く言うと  社内のデータサイロを見つけ、  サイロをつなぐデータパイプラインを構築し、  ビジネスの加速を促す
  • 4.
  • 5.
    本日の内容 【「スピード」と「品質」を実現する】取り組みを紹介 その中でも、テストを中心に紹介 PHP勉強会なので、AspectMockについて詳しく紹介 Angularの技術的な話は。。。 ! ゴール  明日から、AspectMockが使いたくなっちゃう♥
  • 6.
  • 7.
    スピードと品質 計画(スプリント・プランニング) 実装 ユーザに見てもらう(スプリント・レビュー)
  • 8.
    スピードと品質 計画(スプリント・プランニング) サイクルを早く回す 実装 ユーザに見てもらう(スプリント・レビュー)
  • 9.
    サイクルを早く回すために テストは極力自動化 定常的、同じ作業は機械に任せる ! 細かいスパンでデプロイ ユーザは、見て・使ってみないと分からない 頻繁なデプロイが負荷にならないために自動化 ! 技術的負債は残さない 開発者の精神的安定
  • 10.
  • 11.
    開発システム PHP :5.5系 MySQL :5.6系 FuelPHP :1.7.2  ※ 一部システムでは、Phalcon使ったり ! Font-end :AngularJS、TypeScript Test :PHPUnit、AspectMock、Karma、Jasmine、PhantomJS コミュニケーション :Slack ジョブ管理 :SOS JobScheduler ビルド、デプロイ :Grunt、Fabric CI :Jenkins 構成管理 :Gitlab 開発環境 :Vagrant 課題管理 :OpenProject
  • 12.
    開発フローとCI 開発フロー  テストコードを書いて実装完了  Gitlabにマージリクエスト。レビューしてマージ  Jenkinsでテスト、デプロイ  Slackに通知して共有 ! CI  毎朝、実行  Slackに結果を通知
  • 13.
  • 14.
    FuelPHP FulePHPでは極力APIを作るようにする ビュー部分はAngularJSで作りこむ ! 素のController_Restでは、 想定外エラーのレスポンスなど 扱いヅライのでカスタマイズ
  • 15.
    AngularJS 多分に漏れず、DOM地獄から逃げたかった 社内ツールではあるが、UIは今風にしたい ! 秘伝のjQueryソースは無くしたい ある程度、書き方が統一できる
  • 16.
  • 17.
  • 18.
    Grunt + Karma+ Jasmine + PhantomJS Grunt  TypeScriptのコンパイル   Karmaの実行 !  デプロイ、CI   JenkinsでGrunt実行 ! Jasmine APIレスポンスをスタブ化し、 テストが柔軟にできる
  • 19.
  • 20.
    AspectMock モック フレームワーク https://github.com/Codeception/AspectMock ! PHPテストフレームワーク「Codeception」と同じ作者 ! PHPでAOPを実現する「Go-AOP」を使用して メソッドを差し替える仕組み ! Go-AOP https://github.com/lisachenko/go-aop-php AOP:アスペクト志向プログラミング
  • 21.
    なぜAspectMock? テストフルなコード??? ! FuelPHPとの親和性 Fuelは、staticを多様 1.7.2から標準で設定済(core/bootstrap_phpunit.php) ! 単体テストのバリエーションが増える  テストデータ作成に苦労しない 異常ケースが容易にできる
  • 22.
    設定、使い方 達人出版会 「はじめてのフレームワークとし てのFuelPHP第2版(3) 実践編」 ! ※AspectMock使用時のクラスロードエラーの解消の部分だけでも、1000円の価値はありました。 ○ PHP Advent Calendar 2014 kenjisさんの記事 「普通じゃないモッキングフレームワークAspectMockがパワフル過ぎる」   http://blog.a-way-out.net/blog/2014/12/10/aspect-mock/
  • 23.
    Proxy ClassProxy 静的メソッドのMock ! InstansProxy インスタンスのMock ! Test Doubles Builder ClassProxy, InstansProxyを良しなに作成してくれる ! FuncProxy (>= 5.0.0) 指定したNamespaceのファンクションをMock化。 NativeファンクションもMock化可能!!
  • 24.
    こんなときどうする? 1. オブジェクトの中で呼んでいるstaticメソッド 2. DBエラー、ネットワークエラーなどの例外 3. 外部リソースからの取得データ 4. 状態によって戻り値が変わるメソッド DEMOしながら説明します
  • 25.
    まとめ • 定常、定期的な作業は積極的に自動化すべし • テストコードは、テスト実施と同時に書く • AspectMockを使えば、出来ないテストは無い(多分) • SpecメソッドでBDDも可能(試してません) • AspectMockのクセは強いので慣れましょう
  • 26.
  • 27.
  • 28.
  • 29.
    FuelPHP 想定外のエラー発生時も、ちゃんとレスポンスを返すようにする !  protected function response($data = array(), $message = '') {! ! if(!array_key_exists('error_code', $data)) {! ! ! $m_array = Arr::merge(array('error_code' => '200', 'message' => $message), array('data' => $data));! ! }! ! parent::response($m_array);!  }! !  public function router($resource, $arguments) {! ! try {! $ret = parent::router($resource, $arguments);! ! if ($ret === false) {! parent::response(array('error_code' => '403', 'message' => 'Exception'), 403);! }! } catch (Exception $e) {! Log::error($e->getTraceAsString());! Log::error($e->getMessage());! ! parent::response(array('error_code' => '500', 'message' => 'Internal error.'), 500);! }! }!  }!
  • 30.
    AspectMockを使ってみる こんなクラスをMock化してみる <?php! ! namespace Sample;! ! class Model_User extends Model {! ! private $_id;! private $_name;! ! public function __construct($id, $name) {! $this->_id = $id;! $this->_name = $name;! }! ! public function getName() {! return $this->_name;! }! ! public function getDate() {! return date('Y-m-d H:i:s');! }! } <?php! ! namespace Sample;! ! class Model_Suser extends Model {! ! private static $_name = '静的な値';! ! public static function getName() {! return static::$_name;! }! ! public static function callPrivate() {! return static::privatefunc();! }! ! private static function privatefunc() {! $time = FuelCoreDate::time();! return "private:" . $time . PHP_EOL;! }! }
  • 31.
    AspectMockを使ってみる1 <?php! useAspectMockTest as mock;! ! class Test_Sample extends FuelCoreTestCase {! ! protected function setUp() {! Autoloader::add_namespace(! ! ! ! ‘Sample',! ! ! ! APPPATH . 'classes' . DS . 'sample/');! }! ! public function test_インスタンスProxyのケース() {! $user = new SampleModel_User(1, 'TestName');! ! // Mock化! $mock = mock::double($user,! ! ! ! ['getName' => 'DummyName']);! ! // 指定したNamespace内であれば、標準関数もMock化できる! mock::func('Sample', 'date', ‘now!!');! ! $name = $user->getName();! $data = $user->getDate();! ! $mock->verifyInvokedOnce('getName');! $mock->verifyInvokedOnce('getDate');! ! $this->assertEquals('DummyName', $name);! $this->assertEquals('now!!', $data);! }! } public function test_ClassProxyのケース() ! {! ! mock::double('SampleModel_Suser', [! ! ! ! ! ! 'getName' => 'Dummy']);! ! $name = SampleModel_Suser::getName();! ! $this->assertEquals('Dummy', $name);! ! } インスタンスもMock化 クラス名からMock化 標準関数もMock化!
  • 32.
    AspectMockを使ってみる2 /**! *@expectedException FuelException! */! public function test_例外発生ケース() {! 例外を強制的に発生できる DBエラー、ネットワークエラーなど、発生させ難いテストが容易になる ! mock::double('SampleModel_Suser', [! 'getName' => function() {throw new FuelCoreFuelException("例外発生");}! ]);! ! try {! SampleModel_Suser::getName();! ! } catch (FuelException $e) {! ! $this->assertTrue(true);! throw $e;! }! ! $this->assertTrue(false);! }!
  • 33.
    AspectMockを使ってみる3 public functiontest_Privateなメソッドのケース() {! privateメソッドもMock化できる  ※AspectMockの機能ではありませんが、Closureでprivateメソッドが直接呼べる ! // privateメソッドで呼んでるクラスをMock化! mock::double('FuelCoreDate', ['time' => 'hogehoge']);! ! // Privateメソッドを直接呼ぶ! Closure::bind(! function () {! $obj = new SampleModel_Suser();! $ret = $obj->privatefunc();! ! $this->assertEquals('private:hogehoge' . PHP_EOL, $ret);! },! $this,! 'SampleModel_Suser'! )->__invoke();! ! mock::clean();! mock::double('SampleModel_Suser', ['privatefunc' => 'プライベート関数もMock化']);! ! $ret = SampleModel_Suser::callPrivate();! $this->assertEquals('プライベート関数もMock化', $ret);! ! }
  • 34.
    AspectMockを使ってみる4 <?php! namespaceSample;! ! use FuelCoreDB;! ! class Model_Transaction extends Model {! ! public static function transaction() {! ! if(!DB::in_transaction()) {! DB::start_transaction();! }! ! try {! // DB処理! Model_Orm_User::find();! ! DB::commit_transaction();! ! } catch(FuelException $e) {! if(DB::in_transaction()) {! DB::rollback_transaction();! }! ! throw $e;! }! }! }! 【ケース】  例外が発生したときの挙動をテストしたい    1. トランザクションが無ければ貼る  2. 例外が発生しとき、トランザクションが    貼ってあれば、Rollbackする
  • 35.
    AspectMockを使ってみる4 public functiontest_呼び出し回数で挙動を変えるケース() ! {! ! $cnt = 0;! ! $mock = mock::double(! 'FuelCoreDB',! [! 'start_transaction' => true,! 'commit_transaction' => true,! 'rollback_transaction' => true,! 'in_transaction' => function () use (&$cnt) {! if ($cnt == 0) {! $cnt++;! return false;! } elseif ($cnt == 1) {! $cnt++;! return true;! } else {! $cnt++;! return __AM_CONTINUE__; // オリジナルの処理がされる! }! }]);! ! mock::double('SampleModel_Orm_User', [! ! ! ! 'find' => function() {! ! ! ! ! throw new FuelException(“Exception強制発生");}! ! ! ]);! ! try {! SampleModel_Transaction::transaction();! } catch(FuelException $e) {! $mock->verifyInvokedOnce('start_transaction');! $mock->verifyNeverInvoked('commit_transaction');! $mock->verifyInvokedMultipleTimes('in_transaction', 2);! $mock->verifyInvokedOnce('rollback_transaction');! }! } 無名関数の引数に 変数を参照渡しして 呼び出し回数を カウント
  • 36.
    AspectMockを使ってみる5 【ケース】 ORMでfindした値が、 想定どおりの処理が行われた値で saveされるかを確認したい <?php! ! namespace Sample;! ! class Model_Orm_User extends OrmModel! {! ! <?php! ! namespace Sample;! ! class Model_Update extends Model! {! public static function update()! {! $model = Model_Orm_User::find();! ! foreach($model as $ret) {! $ret['val'] = 'hugehuge';! $ret->save();! }! }! }! protected static $_properties = [! 'id',! 'val',! ];! }!
  • 37.
    AspectMockを使ってみる5 確認したいORMクラ スを継承してfindし た結果をダミー値で 定義。 ! saveの引数を無名関 数でチェック <?php! Autoloader::add_namespace(‘Sample',! ! ! ! ! APPPATH . 'classes' . DS . 'sample/');! ! class Tests_Model_StubModel extends SampleModel_Orm_User {! ! protected $_data = [‘id','val'];! ! function __construct($id, $val){! $this->_data['id'] = $id;! $this->_data['val'] = $val;! }! } public function test_ORMでの更新値チェックのケース() {! // ORMでDBから値を取得して処理して更新するパターン! $modify_data = 'hugehuge';! ! $data = new Tests_Model_StubModel('1', 'hogehoge');! ! mock::double('SampleModel_Orm_User',! [! 'find' => function ($param) use ($data) {! // ORMのfindは、レコード単位のORMクラス配列が返る! return [$data];! }! ]);! ! mock::double('OrmModel',[! 'save' => function() use ($modify_data) {! FuelCoreTestCase::assertEquals(! ! ! ! $this->_data['val'], $modify_data! ! ! );! }! ]);! ! SampleModel_Update::update();! }!