SlideShare a Scribd company logo
CodeIgniter + ci-phpunit-test
2016/05/28
Tetsuro Yoshikawa
1 / 64
目次
1. CodeIgniterについて
2. ci-phpunit-testについて
3. テストの書き方
4. まとめ
こんな内容話します。
2 / 64
CodeIgniterって何?
EllisLabによって開発されたPHP FWです。
※現在のオーナーはBCIT(ブリティッシュコロンビア工科大学)
https://www.codeigniter.com/
3 / 64
ライセンス
MIT
アーキテクチャに関するデザインパターン
MVC
生成に関するデザインパターン
Singletonっぽい
動作要件
PHP 5.2.4以上(5.4以上推奨)
4 / 64
CodeIgniterの人気
長期にわたる根強い人気があります!
5 / 64
CodeIgniterのいいところ
6 / 64
CodeIgniterのいいところ
名前がかっこいい
速い・軽い
規約がゆるい
拡張しやすい
学習コストが低い(読みやすい)
etc..
7 / 64
CodeIgniterのわるいところ
8 / 64
CodeIgniterのわるいところ
ない
強いてあげるなら、デフォルトではnamespaceが無い事ぐらいです。
(個人の感想であり個人差があります。)
9 / 64
テストってどうやっているの?
10 / 64
CodeIgniterでは
Unitテストクラスが実装されています。
11 / 64
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
PHPUnit使えないの?
13 / 64
使えます。
そう、ci-phpunit-testならね。
14 / 64
使えます。
そう、ci-phpunit-testならね。
https://github.com/kenjis/ci-phpunit-test
15 / 64
ci-phpunit-testのいいところ
16 / 64
ci-phpunit-testのいいところ
PHPUnitでテストできる
OSS(MITライセンス)
開発者は日本で唯一のCodeIgniter専門書籍の著者(Made in Japan)
require_onceとか書かなくて良い
テストする物によって親クラスが別れる等が無い
今のところ書けないテストが無かった
動かす為にCodeIgniter本体に手を入れる必要が無い
Mockとかが書くのが楽
etc
17 / 64
ci-phpunit-testのわるいところ
18 / 64
ci-phpunit-testのわるいところ
ない
(個人の感想であり個人差があります。)
19 / 64
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 / 64
Modelのテストコード
22 / 64
<?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
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
パラメータ渡してるんだけど
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
<?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
404のテストしたいんだけど
<?php
class Welcome_test extends TestCase {
    public function test_404()
    {
        $this‐>request('GET', 'welcome/_hogehoge');
         $this‐>assertResponseCode(404);
    }
}
assertResponseCodeでレスポンスのテストをする事ができます。
27 / 64
Mock作りたいんだけど
28 / 64
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
<?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
DBからSELECTしてるんだけど
大容量だからテストに15分とかかかる
31 / 64
<?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
<?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
<?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 / 64
<?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
強力なMonkeyPatch機能
(用法用量にご注意ください。)
37 / 64
<?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 / 64
<?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
<?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
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
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
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
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 / 64
<?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
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
ご注意!!!
MonkeyPatchでは置き換える事のできない関数も存在します。
また、MonkeyPatchではテストが実行される直前にコードを差し替え
ています。
そのため、テストの速度に良くない影響を与えます。
用法用量にはご注意ください。
49 / 64
書き方がわからない。
サンプルが欲しい。
50 / 64
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
まとめ
CodeIgniterでPHPUnitを動かすときはci-phpunit-testがオススメ
Controllerはrequestメソッドがオススメ
MockはgetDoubleメソッドがオススメ
どうしようも無い時はMonkeyPatchで回避しましょう
書き方がわからないときはサンプルコードかドキュメントを読み
ましょう
52 / 64
おまけ。SQLをテストしたい例
53 / 64
<?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
凄く複雑な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
テストコードでの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
ci-phpunit-testで
良いCodeIgniterライフを
送りましょう!
57 / 64
自己紹介
Tetsuro Yoshikawa
Twitter @iBotchME
株式会社音生
早朝意識弱い系マークアップエンジニア
PHP(嗜む程度)
HTML(少し)
CSS(少々)
JavaScript(嗜む程度)
58 / 64
宣伝
日本CodeIgniterユーザ会では翻訳作業をしています!!
皆さんでCodeIgniterを盛り上げましょう!
翻訳方法
http://codeigniter-jp.github.io/user_guide_src_ja/ へアクセス
59 / 64
翻訳方法1
GitHubで修正をクリック
60 / 64
翻訳方法2
鉛筆ボタンのクリック
61 / 64
翻訳方法3
翻訳して「Propose file change」をクリック
62 / 64
翻訳方法4
「Create pull request」をクリック
63 / 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

More Related Content

ODP
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
PPTX
PHP AST 徹底解説
PPTX
インセプションデッキ: やらないことリストと トレードオフスライダーをやってる話
PDF
MySQLで論理削除と正しく付き合う方法
PDF
C++でできる!OS自作入門
PDF
ruby-ffiについてざっくり解説
PDF
PostgreSQLアンチパターン
PPTX
C#とILとネイティブと
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
PHP AST 徹底解説
インセプションデッキ: やらないことリストと トレードオフスライダーをやってる話
MySQLで論理削除と正しく付き合う方法
C++でできる!OS自作入門
ruby-ffiについてざっくり解説
PostgreSQLアンチパターン
C#とILとネイティブと

What's hot (20)

PDF
Twitterのsnowflakeについて
PDF
Wowzaを用いた配信基盤 Takusuta tech conf01
PDF
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
PPTX
AVX-512(フォーマット)詳解
KEY
ラムダ計算入門
PDF
テスト駆動開発のはじめ方
PPTX
分散システムについて語らせてくれ
PDF
オブジェクト指向できていますか?
PDF
技術紹介: S2E: Selective Symbolic Execution Engine
PDF
Constexpr 中3女子テクニック
PDF
C++ マルチスレッドプログラミング
PDF
systemdを始めよう
PDF
こんなに使える!今どきのAPIドキュメンテーションツール
PDF
ワタシはSingletonがキライだ
PDF
ネットワーク ゲームにおけるTCPとUDPの使い分け
PDF
Linux女子部 iptables復習編
PPTX
shared_ptrとゲームプログラミングでのメモリ管理
PDF
何となく勉強した気分になれるパーサ入門
PPTX
Verilator勉強会 2021/05/29
PDF
マイクロにしすぎた結果がこれだよ!
Twitterのsnowflakeについて
Wowzaを用いた配信基盤 Takusuta tech conf01
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
AVX-512(フォーマット)詳解
ラムダ計算入門
テスト駆動開発のはじめ方
分散システムについて語らせてくれ
オブジェクト指向できていますか?
技術紹介: S2E: Selective Symbolic Execution Engine
Constexpr 中3女子テクニック
C++ マルチスレッドプログラミング
systemdを始めよう
こんなに使える!今どきのAPIドキュメンテーションツール
ワタシはSingletonがキライだ
ネットワーク ゲームにおけるTCPとUDPの使い分け
Linux女子部 iptables復習編
shared_ptrとゲームプログラミングでのメモリ管理
何となく勉強した気分になれるパーサ入門
Verilator勉強会 2021/05/29
マイクロにしすぎた結果がこれだよ!
Ad

Viewers also liked (20)

PDF
Codeigniter4の比較と検証
PDF
継続的Webセキュリティテスト PHPカンファレンス関西2015 LT
PDF
Wocker 秒速で WordPress 開発環境を構築する
PPTX
FuelPHP × HHVM サービス開発事例
PPTX
Idcfクラウド 初心者の事始め(2)資料
PDF
PHPerのための(不遇に負けない)Selenium入門 @ FuelPHP&CodeIgniter ユーザの集い #9
PDF
PHPカンファレンス2016 協賛のご案内
PPTX
2017 02-14 キュー実装に見る排他処理
PPTX
PHPCON fukuoka 2015 CodeIgniter update
PPTX
OmegaTでドキュメント翻訳
PPTX
REST API Best Practices & Implementing in Codeigniter
PDF
RESTful API Design & Implementation with CodeIgniter PHP Framework
PPTX
Proyecto ASHYI
PDF
Opinieartikel FD: Nederlandse pensioensector staat met rug naar Europa
PPTX
טיפים לחשיבת הצלחה לכבוד 2017
PPT
Género y desastres en Chile-Itzá Castañeda
PDF
Revue de presse IoT / Data du 04/03/2017
PDF
0620 w15 qp_11
PDF
Webinar: 5 Things to Prepare Your Company for Growth - 2-time CEO, Matt Peterson
Codeigniter4の比較と検証
継続的Webセキュリティテスト PHPカンファレンス関西2015 LT
Wocker 秒速で WordPress 開発環境を構築する
FuelPHP × HHVM サービス開発事例
Idcfクラウド 初心者の事始め(2)資料
PHPerのための(不遇に負けない)Selenium入門 @ FuelPHP&CodeIgniter ユーザの集い #9
PHPカンファレンス2016 協賛のご案内
2017 02-14 キュー実装に見る排他処理
PHPCON fukuoka 2015 CodeIgniter update
OmegaTでドキュメント翻訳
REST API Best Practices & Implementing in Codeigniter
RESTful API Design & Implementation with CodeIgniter PHP Framework
Proyecto ASHYI
Opinieartikel FD: Nederlandse pensioensector staat met rug naar Europa
טיפים לחשיבת הצלחה לכבוד 2017
Género y desastres en Chile-Itzá Castañeda
Revue de presse IoT / Data du 04/03/2017
0620 w15 qp_11
Webinar: 5 Things to Prepare Your Company for Growth - 2-time CEO, Matt Peterson
Ad

Similar to Code igniter + ci phpunit-test (20)

PDF
Code ignitertalk 01
PDF
Php勉強会資料20090629
PDF
PHPカンファレンス関西 2011
PDF
Code igniterでテスト駆動開発 資料作成中
PDF
PHPUnitでリファクタリング
PDF
Testing PHP extension on Travis CI
PDF
CodeIgniter をモダンに改造してみた
PDF
はじめてのCodeIgniter
PDF
phpmatsuri2013 LT大会 資料
PDF
CodeIgniter 貴方はどのフレームワークを使うべきか?
PDF
Getting Started with Testing using PHPUnit
PDF
PHPUnit でテスト駆動開発を始めよう
PDF
2011 PHPカンファレンス関西 懇親会LT
PPT
Code Igniterについて
PDF
CodeIgniter 最新情報 2011 (増補版)
PDF
PHPとTravis CIでブラウザテスト
PDF
Introduction to Continuous Test Runner MakeGood
PDF
CodeIgniterによるPhwittr
PDF
CodeIgniter 〜 2008年大躍進のPHPフレームワーク
PDF
PHP勉強会 #51
Code ignitertalk 01
Php勉強会資料20090629
PHPカンファレンス関西 2011
Code igniterでテスト駆動開発 資料作成中
PHPUnitでリファクタリング
Testing PHP extension on Travis CI
CodeIgniter をモダンに改造してみた
はじめてのCodeIgniter
phpmatsuri2013 LT大会 資料
CodeIgniter 貴方はどのフレームワークを使うべきか?
Getting Started with Testing using PHPUnit
PHPUnit でテスト駆動開発を始めよう
2011 PHPカンファレンス関西 懇親会LT
Code Igniterについて
CodeIgniter 最新情報 2011 (増補版)
PHPとTravis CIでブラウザテスト
Introduction to Continuous Test Runner MakeGood
CodeIgniterによるPhwittr
CodeIgniter 〜 2008年大躍進のPHPフレームワーク
PHP勉強会 #51

Code igniter + ci phpunit-test