Download - Code igniter + ci phpunit-test
CodeIgniter + ci-phpunit-test2016/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テストクラスでのテスト<?phpclass 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
<?phpclass 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のテストコード
<?phpclass 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
<?phpclass 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
<?phpclass 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のテストしたいんだけど<?phpclass Welcome_test extends TestCase {
public function test_404() { $this‐>request('GET', 'welcome/_hogehoge'); $this‐>assertResponseCode(404); }}
assertResponseCodeでレスポンスのテストをする事ができます。
27 / 64
Mock作りたいんだけど
28 / 64
PHPUnitでのMock作成<?phpclass 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
<?phpclass 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
<?phpclass 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
<?phpclass 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
<?phpclass 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
<?phpclass 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
<?phpclass 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
<?phpclass 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
<?phpclass 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でログイン判定している<?phpclass 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の読み出し前にロードさせる
<?phpclass 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で認証してるんですが<?phpclass 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を使いましょう<?phpclass 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
<?phpclass 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で定数も書き換え可能です。
<?phpclass 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
<?phpclass 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のご紹介
ciphpunittestに同梱されているDBフィクスチャ用のライブラリ
<?phpclass 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の呼び出し<?phpclass 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