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.

Code igniter + ci phpunit-test

3.285 visualizaciones

Publicado el

Nagoya OpenSource Conference 2016
CodeIgniter+ci-phpunit-test

Publicado en: Ingeniería
  • Codeigniter phpunit (https://www.cloudways.com/blog/codeigniter-unit-testing/ ) integration is really easy to do. Main thing is to create a test case in Codeigniter. For that, you have to use configuration objects as the base.
       Responder 
    ¿Estás seguro?    No
    Tu mensaje aparecerá aquí

Code igniter + ci phpunit-test

  1. 1. CodeIgniter + ci-phpunit-test 2016/05/28 Tetsuro Yoshikawa 1 / 64
  2. 2. 目次 1. CodeIgniterについて 2. ci-phpunit-testについて 3. テストの書き方 4. まとめ こんな内容話します。 2 / 64
  3. 3. CodeIgniterって何? EllisLabによって開発されたPHP FWです。 ※現在のオーナーはBCIT(ブリティッシュコロンビア工科大学) https://www.codeigniter.com/ 3 / 64
  4. 4. ライセンス MIT アーキテクチャに関するデザインパターン MVC 生成に関するデザインパターン Singletonっぽい 動作要件 PHP 5.2.4以上(5.4以上推奨) 4 / 64
  5. 5. CodeIgniterの人気 長期にわたる根強い人気があります! 5 / 64
  6. 6. CodeIgniterのいいところ 6 / 64
  7. 7. CodeIgniterのいいところ 名前がかっこいい 速い・軽い 規約がゆるい 拡張しやすい 学習コストが低い(読みやすい) etc.. 7 / 64
  8. 8. CodeIgniterのわるいところ 8 / 64
  9. 9. CodeIgniterのわるいところ ない 強いてあげるなら、デフォルトではnamespaceが無い事ぐらいです。 (個人の感想であり個人差があります。) 9 / 64
  10. 10. テストってどうやっているの? 10 / 64
  11. 11. CodeIgniterでは Unitテストクラスが実装されています。 11 / 64
  12. 12. Unitテストクラスでのテスト <?php class Auth_model_test extends CI_Controller {     public function __construct()     {         parent::__construct();         if ( ENVIRONMENT !== 'production' ) show_404();          $this‐>load‐>library('unit_test');     }     public function test_is_loggedin()     {         $this‐>load‐>model('auth_model');         $result = $this‐>auth_model‐>is_loggedin();          echo $this‐>unit‐>run($result,               FALSE,               'Auth_model::is_loggedin');     } } 12 / 64
  13. 13. PHPUnit使えないの? 13 / 64
  14. 14. 使えます。 そう、ci-phpunit-testならね。 14 / 64
  15. 15. 使えます。 そう、ci-phpunit-testならね。 https://github.com/kenjis/ci-phpunit-test 15 / 64
  16. 16. ci-phpunit-testのいいところ 16 / 64
  17. 17. ci-phpunit-testのいいところ PHPUnitでテストできる OSS(MITライセンス) 開発者は日本で唯一のCodeIgniter専門書籍の著者(Made in Japan) require_onceとか書かなくて良い テストする物によって親クラスが別れる等が無い 今のところ書けないテストが無かった 動かす為にCodeIgniter本体に手を入れる必要が無い Mockとかが書くのが楽 etc 17 / 64
  18. 18. ci-phpunit-testのわるいところ 18 / 64
  19. 19. ci-phpunit-testのわるいところ ない (個人の感想であり個人差があります。) 19 / 64
  20. 20. ci-phpunit-testって どうやって導入するの? $ cd CodeIgniter設置場所(CI index.php ) $ composer require kenjis/ci‐phpunit‐test ‐‐dev $ php vendor/kenjis/ci‐phpunit‐test/install.php これでapplications/testsにてテストが書ける様になっています。 20 / 64
  21. 21. 実際にコードをご覧下さい。 21 / 64
  22. 22. Modelのテストコード 22 / 64
  23. 23. <?php class Test_model_test extends TestCase {     public function setUp()     {          $this‐>resetInstance();          $this‐>CI‐>load‐>model('Test_model');          $this‐>obj = $this‐>CI‐>Test_model;  //obj 変数 model 代入     }     public function test_get_list()     {         $assert_list = [             1 => 'hogehoge',             2 => 'fugafuga'         ];         $list = $this‐>obj‐>get_list();         foreach ( $list as $val )         {             $this‐>assertEquals($assert_list[$val‐>id], $val‐>name);         }     } } 23 / 64
  24. 24. Controllerのテストコード <?php class Hoge_test extends TestCase {     public function test_index()     {         //request method 設定 controller 書          $output = $this‐>request('GET', 'hoge/index');         $this‐>assertContains('<title>hogehoge</title>', $output);     } } 24 / 64
  25. 25. パラメータ渡してるんだけど controllers/Hoge.php <?php class Hoge extends CI_Controller {     public function index()     {         $this‐>load‐>view('hoge/index');     } } views/hoge/index.php <!DOCTYPE html> <html lang="ja">     <meta charset="UTF‐8">     <title></title>      <span><?php echo html_escape($this‐>input‐>post('foo'));?></span> </html> 25 / 64
  26. 26. <?php class Hoge_test extends TestCase {     public function test_index()     {         //第三引数 渡         $output = $this‐>request('POST', 'hoge/index', [              'foo' => 'bar'          ]);         $this‐>assertContains('<span>bar</span>', $output);     } } string型で読み込みストリームへパラメータを渡す事もできます。 また、第二引数にstring型でGETパラメータを渡す事もできます。 26 / 64
  27. 27. 404のテストしたいんだけど <?php class Welcome_test extends TestCase {     public function test_404()     {         $this‐>request('GET', 'welcome/_hogehoge');          $this‐>assertResponseCode(404);     } } assertResponseCodeでレスポンスのテストをする事ができます。 27 / 64
  28. 28. Mock作りたいんだけど 28 / 64
  29. 29. PHPUnitでのMock作成 <?php class Auth_model_test extends PHPUnit_Framework_TestCase { //...     public function test_is_loggedin()     {         //Mock 作成         $mock = $this‐>getMockBuilder('Auth_model')             ‐>setMethods('is_loggedin')             ‐>getMock();         //返 値 設定         $mock‐>expects($this‐>any())             ‐>method('is_loggedin')             ‐>willRetrun(TRUE);         $this‐>assertTrue($mock‐>is_loggedin());     } } 29 / 64
  30. 30. <?php class Dashboard_test extends TestCase {     public function test_index()     {         $this‐>request‐>setCallable(function($CI){             // 判定用             // getMockBuilder('Auth_model')             // ‐>setMethods('is_loggedin')             // ‐>getMock() 必要              $auth = $this‐>getDouble('Auth_model', ['is_loggedin' => TRUE]);             $CI‐>auth_model = $auth;         });         $output = $this‐>request('GET', 'dashboard/index');         $this‐>assertContains('認証済', $output);     } } getDoubleで簡単にMock作成 ControllerのテストではsetCallableでMockをセット 30 / 64
  31. 31. DBからSELECTしてるんだけど 大容量だからテストに15分とかかかる 31 / 64
  32. 32. <?php class Hoge_model extends CI_Model {     public function get_large_capacity()     {         $this‐>db‐>select('id');          $this‐>db‐>join('(SELECT SLEEP(900)) AS SL ',              '1 = 1',              'LEFT',              FALSE);         $query = $this‐>db‐>get('large_capacity');         return $query‐>result();     } } 32 / 64
  33. 33. <?php class Hoge_model extends CI_Model {     public function get_large_capacity()     {         $this‐>db‐>select('id');          $this‐>db‐>join('(SELECT SLEEP(900)) AS SL ',              '1 = 1',              'LEFT',              FALSE);         $query = $this‐>db‐>get('large_capacity');         return $query‐>result();     } } 謎のSLEEP 33 / 64
  34. 34. <?php class Hoge_model_test extends TestCase {     public function test_get_large_capacity()     {         //返 値 設定         $return = [(object)['id' => 1]];         //CI DB driver 訳 Mock作成          $db_result = $this‐>getDouble('CI_DB_pdo_result', [              'result' => $return          ]);          $db = $this‐>getDouble('CI_DB_pdo_mysql_driver', [              'get' => $db_result          ]);         $this‐>verifyInvokedOnce($db_result, 'result',[]);         $this‐>verifyInvokedOnce($db, 'get', ['large_capacity']);          $this‐>obj‐>db  = $db;         $large_capacity = $this‐>obj‐>get_large_capacity();         $this‐>assertEquals($large_capacity[0]‐>id, 1);     } } verifyInvokedOnceでどんな引数を渡しているかも検証 34 / 64
  35. 35. このコードどうテストしよう 35 / 64
  36. 36. <?php class Api_model extends CI_Model {     public function get_api_key()     {         while (TRUE)         {             // mt_rand              $result = md5(uniqid(mt_rand(), TRUE));             if ( ! $this‐>key_exists($result) ) break;         }         $this‐>add_api_key($result);         return $result;     } オブジェクトじゃないのでMockが作れない 36 / 64
  37. 37. 強力なMonkeyPatch機能 (用法用量にご注意ください。) 37 / 64
  38. 38. <?php class Api_test extends TestCase { //...     public function test_get_api_key()     {         //patchFunction 関数 挙動 制御          MonkeyPatch::patchFunction('md5',               ' md5 ',               'Api_model::get_api_key');         $db_result = $this‐>getDouble('CI_DB_pdo_result', ['num_rows' => 0]);         $db = $this‐>getDouble('CI_DB_pdo_mysql_driver', [             'insert' => $db_result,             'get'    => $db_result,             'where'  => TRUE         ]);         $this‐>api_model‐>db = $db;         $api_key = $this‐>api_model‐>get_api_key();         $this‐>assertEquals(' md5 ', $api_key);     } patchFunctionでmd5の挙動を制御 38 / 64
  39. 39. さらにややこしい 39 / 64
  40. 40. <?php class Api_model extends CI_Model { //...     public function create_random_key()     {         $result = md5(uniqid(mt_rand(), TRUE));          if (function_exists('random_bytes'))         {             // 中             $result = hash_hmac('sha256',                  random_bytes(32),                  random_bytes(16));         }          elseif (function_exists('openssl_random_pseudo_bytes'))         {             //             $result = hash_hmac('sha256',                  openssl_random_pseudo_bytes(32),                  openssl_random_pseudo_bytes(16));         }         return $result;     } 同じfunction_exists関数を使って判定している。 40 / 64
  41. 41. <?php class Api_test extends TestCase { //...     public function test_create_random_key()     {         // 第2引数 指定          MonkeyPatch::patchFunction('function_exists', function($func){              return (bool)( $func !== 'random_bytes' );          }, 'Api_model::get_api_key');         MonkeyPatch::patchFunction('hash_hmac',              'openssl_random_pseudo_bytes !',              'Api_model::get_api_key');         $api_key = $this‐>api_model‐>create_random_key();         $this‐>assertEquals('openssl_random_pseudo_bytes !', $api_key);     } これもpatchFunctionで対応可能 41 / 64
  42. 42. constructorでログイン判定している <?php class Mypage extends CI_Controller {     public function __construct()     {         parent::__construct();         $this‐>load‐>library('Ion_auth');         $this‐>load‐>helper('url_helper');         if ( ! $this‐>ion_auth‐>logged_in() )         {             redirect('login');         }     } } 42 / 64
  43. 43. CI_Controllerの読み出し前にロードさせる <?php class Mypage_test extends TestCase {     public function test_index()     {         $this‐>setCallablePreConstructor(function(){             $auth = $this‐>getDouble(                 'Ion_auth', ['logged_in' => TRUE]             );             //CI load_class相当 動作 ion_auth Mock 挿入              load_class_instance('ion_auth', $auth);         });         $output = $this‐>request('GET', 'mypage/index');         $this‐>assertContains('<span> </span>', $output);     } } ci-phpunit-testのsetCallablePreConstructorでCI_Controller インスタ ンス生成前にhook 43 / 64
  44. 44. modelで認証してるんですが <?php class Mypage extends CI_Controller {     public function __construct()     {         parent::__construct();         $this‐>load‐>model('auth_model');         $this‐>load‐>helper('url_helper');         if ( ! $this‐>auth_model‐>is_loggedin() )         {             redirect('login');         }     } } 44 / 64
  45. 45. MonkeyPatchを使いましょう <?php class Mypage_test extends TestCase {     public function test_index()     {          MonkeyPatch::patchMethod('Auth_model', ['is_loggedin' => TRUE]);         $output = $this‐>request('GET', 'mypage/index');         $this‐>assertContains('<span> </span>', $output);     } } getDoubleみたいな書き方で設定できます。 45 / 64
  46. 46. 定数によって 認証を振り分けている 46 / 64
  47. 47. <?php class Auth_model extends CI_Model { //...     public function is_loggedin()     {         $uid = $this‐>session‐>userdata('user_id');          if ( ENVIRONMENT !== 'production' )         {             $uid = 1;         }         if ( empty($uid) )         {             return FALSE;         }         $user_data = $this‐>get($uid);         return ( ! empty($user_data) );     } } 47 / 64
  48. 48. MonkeyPatchで定数も書き換え可能です。 <?php class Auth_model_test extends TestCase { //...     public function test_is_loggedin_develop()     {         //development 置 換          MonkeyPatch::patchConstant('ENVIRONMENT',               'production',               'Auth_model::is_loggedin');         $sess_mock = $this‐>getDouble('CI_Session', ['userdata' => 2]);         $this‐>auth_model‐>session = $sess_mock;         $this‐>assertFalse($this‐>auth_model‐>is_loggedin());     } } 48 / 64
  49. 49. ご注意!!! MonkeyPatchでは置き換える事のできない関数も存在します。 また、MonkeyPatchではテストが実行される直前にコードを差し替え ています。 そのため、テストの速度に良くない影響を与えます。 用法用量にはご注意ください。 49 / 64
  50. 50. 書き方がわからない。 サンプルが欲しい。 50 / 64
  51. 51. Documentが揃ってます。 サンプルコードあります。 Document https://github.com/kenjis/ci-phpunit- test/blob/master/docs/HowToWriteTests.md サンプルコード https://github.com/kenjis/ci-app-for-ci-phpunit- test/tree/v0.12.0/application/tests 51 / 64
  52. 52. まとめ CodeIgniterでPHPUnitを動かすときはci-phpunit-testがオススメ Controllerはrequestメソッドがオススメ MockはgetDoubleメソッドがオススメ どうしようも無い時はMonkeyPatchで回避しましょう 書き方がわからないときはサンプルコードかドキュメントを読み ましょう 52 / 64
  53. 53. おまけ。SQLをテストしたい例 53 / 64
  54. 54. <?php class Hoge_model_test extends TestCase {     public function test_get_large_capacity()     {         //返 値 設定         $return = [(object)['id' => 1]];         //CI DB driver 訳 Mock作成          $db_result = $this‐>getDouble('CI_DB_pdo_result', [              'result' => $return          ]);          $db = $this‐>getDouble('CI_DB_pdo_mysql_driver', [              'get' => $db_result          ]);         $this‐>verifyInvokedOnce($db_result, 'result',[]);         $this‐>verifyInvokedOnce($db, 'get', ['large_capacity']);          $this‐>obj‐>db  = $db;         $large_capacity = $this‐>obj‐>get_large_capacity();         $this‐>assertEquals($large_capacity[0]‐>id, 1);     } } さっきの15分かかるテストの例とは逆に 54 / 64
  55. 55. 凄く複雑なSQLを使っているからSQLのテストも含めてテストした Seederのご紹介 ci­phpunit­testに同梱されているDBフィクスチャ用のライブ ラリ <?php class AuthSeeder extends Seeder {     private $table = 'users';     public function run()     {         $this‐>db‐>truncate($this‐>table);         $data = [             'id'       => 1,             'username' => 'unit_test',             'password' => 'unit_test'         ];         $this‐>db‐>insert($this‐>table, $data);     } } 55 / 64
  56. 56. テストコードでのSeederの呼び出し <?php class Auth_model_test extends TestCase {     public function setUpBeforeClass()     {         parent::setUpBeforeClass();         $CI =& get_instance();          $CI‐>load‐>library('Seeder');          $CI‐>seeder‐>call('AuthSeeder');     } //... setUpやsetUpBeforeClass等でロードして呼び出すだけ ただし、CodeIgniterのDB Driverのテストがしたい訳では無いと思う ので使う事は稀です。 56 / 64
  57. 57. ci-phpunit-testで 良いCodeIgniterライフを 送りましょう! 57 / 64
  58. 58. 自己紹介 Tetsuro Yoshikawa Twitter @iBotchME 株式会社音生 早朝意識弱い系マークアップエンジニア PHP(嗜む程度) HTML(少し) CSS(少々) JavaScript(嗜む程度) 58 / 64
  59. 59. 宣伝 日本CodeIgniterユーザ会では翻訳作業をしています!! 皆さんでCodeIgniterを盛り上げましょう! 翻訳方法 http://codeigniter-jp.github.io/user_guide_src_ja/ へアクセス 59 / 64
  60. 60. 翻訳方法1 GitHubで修正をクリック 60 / 64
  61. 61. 翻訳方法2 鉛筆ボタンのクリック 61 / 64
  62. 62. 翻訳方法3 翻訳して「Propose file change」をクリック 62 / 64
  63. 63. 翻訳方法4 「Create pull request」をクリック 63 / 64
  64. 64. 翻訳方法まとめ 1. http://codeigniter-jp.github.io/user_guide_src_ja/ 2. 翻訳したいページでGitHubで修正をクリック 3. GitHubにログインして鉛筆ボタンクリック 4. 翻訳して「Propose file change」をクリック 5. 確認して「Create pull request」をクリック 64 / 64

×