Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.

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

7.902 visualizaciones

Publicado el

2014年12月09日にヒカ☆ラボに登壇された株式会社インテリジェンスの清田氏のスライド資料です。

Publicado en: Ingeniería
  • Sé el primero en comentar

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

  1. 1. 「スピード」と「品質」を実現する PHP開発チームの取り組み !  ~AngularJS + FuelPHP + AspectMock~
  2. 2. 何者? 株式会社インテリジェンス  マーケティング企画統括部  サービス開発部 テクノロジーグループ ! 清田 馨一郎  Twitter:@seikei1874 【経歴】 2002年 SIerに入社 PGから叩き上げでPMまで経験 大手企業の基幹システムからソーシャルゲーム開発まで幅広く経験   2014年04月から、インテリジェンスへJOIN 石抱き
  3. 3. 何してる? サービス開発部  dots.(http://eventdots.jp/)の開発、運営  マーケティング部門の業務改善、見える化  … etc ! (私の)ミッションを格好良く言うと  社内のデータサイロを見つけ、  サイロをつなぐデータパイプラインを構築し、  ビジネスの加速を促す
  4. 4. 本日の内容
  5. 5. 本日の内容 【「スピード」と「品質」を実現する】取り組みを紹介 その中でも、テストを中心に紹介 PHP勉強会なので、AspectMockについて詳しく紹介 Angularの技術的な話は。。。 ! ゴール  明日から、AspectMockが使いたくなっちゃう♥
  6. 6. スピードと品質
  7. 7. スピードと品質 計画(スプリント・プランニング) 実装 ユーザに見てもらう(スプリント・レビュー)
  8. 8. スピードと品質 計画(スプリント・プランニング) サイクルを早く回す 実装 ユーザに見てもらう(スプリント・レビュー)
  9. 9. サイクルを早く回すために テストは極力自動化 定常的、同じ作業は機械に任せる ! 細かいスパンでデプロイ ユーザは、見て・使ってみないと分からない 頻繁なデプロイが負荷にならないために自動化 ! 技術的負債は残さない 開発者の精神的安定
  10. 10. 開発
  11. 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. 12. 開発フローとCI 開発フロー  テストコードを書いて実装完了  Gitlabにマージリクエスト。レビューしてマージ  Jenkinsでテスト、デプロイ  Slackに通知して共有 ! CI  毎朝、実行  Slackに結果を通知
  13. 13. フロントサイド
  14. 14. FuelPHP FulePHPでは極力APIを作るようにする ビュー部分はAngularJSで作りこむ ! 素のController_Restでは、 想定外エラーのレスポンスなど 扱いヅライのでカスタマイズ
  15. 15. AngularJS 多分に漏れず、DOM地獄から逃げたかった 社内ツールではあるが、UIは今風にしたい ! 秘伝のjQueryソースは無くしたい ある程度、書き方が統一できる
  16. 16. TypeScript コンパイルを通すので、構文チェックができる。 デバッグでエラー箇所が特定し易い → 作業効率は上がる
  17. 17. Wijmo(ウィジモ) リッチUIを提供するJavaScriptライブラリ データグリッドやチャートなどのWidgetを多数提供 AngularのDirectiveが標準で提供 !
  18. 18. Grunt + Karma + Jasmine + PhantomJS Grunt  TypeScriptのコンパイル   Karmaの実行 !  デプロイ、CI   JenkinsでGrunt実行 ! Jasmine APIレスポンスをスタブ化し、 テストが柔軟にできる
  19. 19. AspectMock
  20. 20. AspectMock モック フレームワーク https://github.com/Codeception/AspectMock ! PHPテストフレームワーク「Codeception」と同じ作者 ! PHPでAOPを実現する「Go-AOP」を使用して メソッドを差し替える仕組み ! Go-AOP https://github.com/lisachenko/go-aop-php AOP:アスペクト志向プログラミング
  21. 21. なぜAspectMock? テストフルなコード??? ! FuelPHPとの親和性 Fuelは、staticを多様 1.7.2から標準で設定済(core/bootstrap_phpunit.php) ! 単体テストのバリエーションが増える  テストデータ作成に苦労しない 異常ケースが容易にできる
  22. 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. 23. Proxy ClassProxy 静的メソッドのMock ! InstansProxy インスタンスのMock ! Test Doubles Builder ClassProxy, InstansProxyを良しなに作成してくれる ! FuncProxy (>= 5.0.0) 指定したNamespaceのファンクションをMock化。 NativeファンクションもMock化可能!!
  24. 24. こんなときどうする? 1. オブジェクトの中で呼んでいるstaticメソッド 2. DBエラー、ネットワークエラーなどの例外 3. 外部リソースからの取得データ 4. 状態によって戻り値が変わるメソッド DEMOしながら説明します
  25. 25. まとめ • 定常、定期的な作業は積極的に自動化すべし • テストコードは、テスト実施と同時に書く • AspectMockを使えば、出来ないテストは無い(多分) • SpecメソッドでBDDも可能(試してません) • AspectMockのクセは強いので慣れましょう
  26. 26. ご質問ありますか? http://eventdots.jp/
  27. 27. ありがとうございました! http://eventdots.jp/ いっしょに働く仲間を募集中!!!
  28. 28. 付録
  29. 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. 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. 31. 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化!
  32. 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. 33. 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);! ! }
  34. 34. 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する
  35. 35. 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');! }! } 無名関数の引数に 変数を参照渡しして 呼び出し回数を カウント
  36. 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. 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();! }!

×