関西php勉強会のlimeの話
TRANSCRIPT
オブジェクト指向抜きでTDDしたいなら
Limeを使うといいよ
@tanakahisateru
自己紹介
タナカヒサテル@tanakahisateru
PHPのフレームワークを開発しています
Pinoco https://github.com/tanakahisateru/pinoco
Lime Testing Framework
し…Symfonyだと!!
大丈夫、Limeは単独で使えるから。しかも…
超 簡 単
ダウンロードhttp://trac.symfony-project.org/wiki/LimeTestingFramework
ファイル1個だけ
こんな感じ
テストと実装を足すとこんな感じ
テストの記述<?phprequire_once('lime.php');require_once('utils.php'); // これからテストするソース
$lime = new lime_test();
$zenkaku_tel = "06−6543−9876";$hankaku_tel = to_alphanum($zenkaku_tel);
$lime->is($hankaku_tel,"06-6543-9876");
utils-test.php
●単体ファイルをrequire_onceするだけで使えるようになる。●テストケースにclassもfunctionも書かなくていい。●assertEquals → is (短い)
テストを実行$ php utils-test.php
1..2PHP Fatal error: Call to undefined function to_alphanum() in /Users/tanakahisateru/Desktop/limetest/utils-test.php on line 8
コマンドラインからutils-test.phpスクリプトを実行すると、失敗。to_alphnumメソッドが実装されていないことがわかる。
PHPでは「関数がない」とか「シンタックスエラーがある」という、コンパイラを通る言語なら普通起こらないようなミスが起こる。すべてのソースをいちどrequireして関数コールを試しておく。これコンパイルエラーのチェックと同じぐらい大事なこと。
仮の実装<?phpfunction utils_to_alphanum($str){ return $str;}
utils.php
テストの実行$ php utils-test.php
1..1not ok 1# Failed test (./utils-test.php at line 10)# got: '06−6543−9876'# expected: '06-6543-9876'# Looks like you failed 1 tests of 1.
もちろん失敗するよね。
実装する<?phpfunction to_alphanum($str){ return str_replace( array( '0','1','2','3','4','5','6','7','8','9','−' ), array('0','1','2','3','4','5','6','7','8','9','-'), $str );}
utils.php
テスト駆動開発では、 「テストコードをクリアする最少の実装」を書くようにする。これポイント。最初から万能な実装を書いてしまうと、テストを成長させるモチベーションがなくなってしまう。
テストの実行$ php utils-test.php
1..1ok 1# Looks like everything went fine.
$lime->is($hankaku_tel, "06-6543-9876"); というテストが期待通り動いた。
テストケースの追加<?phprequire_once('lime.php');require_once('utils.php');
$lime = new lime_test();
$zenkaku_tel = "06−6543−9876";$hankaku_tel = to_alphanum($zenkaku_tel);$lime->is($hankaku_tel, "06-6543-9876");
$zenkaku_email = "tanakahisateru@gmail.com";$hankaku_email = to_alphanum($zenkaku_email);$lime->is($hankaku_email, "[email protected]");
テストの実行$ php utils-test.php
1..1ok 1not ok 2# Failed test (./utils-test.php at line 13)# got: 'tanakahisateru@gmail.com'# expected: '[email protected]'# Looks like you planned 1 tests but ran 1 extra. # Looks like you failed 1 tests of 2.
もちろん新しく追加したテストは失敗する。
で、実装する…の?<?phpfunction to_alphanum($str){ return str_replace( array( '0','1','2','3','4','5','6','7','8','9','−', 'a','b','c','d','e','f','g','h','i','j','k', 'l','m','n','o','p','q','r','s','t','u','v', 'w','x','y','z','_','.','@' ), array( '0','1','2','3','4','5','6','7','8','9','-', 'a','b','c','d','e','f','g','h','i','j','k', 'l','m','n','o','p','q','r','s','t','u','v', 'w','x','y','z','_','.','@' ), $str );}
utils.php
まあテストは成功だけど$ php utils-test.php
ok 1ok 21..2# Looks like everything went fine.
この調子だと次のテストを書く気が起こらない。
リファクタリングなど<?phpfunction to_alphanum($str){ return mb_convert_kana($str, 'a');}
utils.php
よし、書き換えよう。たぶんこの関数で同じ変換いけると思う。たぶん…
おっとこれは!!
$ php utils-test.php
not ok 1# Failed test (./utils-test.php at line 9)# got: '06−6543−9876'# expected: '06-6543-9876'not ok 2# Failed test (./utils-test.php at line 13)# got: 'tanakahisateru@gmail.com'# expected: '[email protected]'1..2# Looks like you failed 2 tests of 2.
やばいやばい忘れてた<?phpfunction to_alphanum($str, $encoding='utf-8'){ return mb_convert_kana($str, 'a', $encoding);}
テストがあって良かった$ php utils-test.php
ok 1ok 21..2# Looks like everything went fine.
機能が劣化したらすぐにわかる。これほんとTDDのおいしさ。
使い方はこちらで
http://www.symfony-project.org/gentle-introduction/1_4/ja/15-Unit-and-Functional-Testing
と、まあこんなやりかたで
PinocoはLimeでTDDしました
Lime単体でTDDやってみて● コマンドラインのスクロール量がたいへん
● テスト項目が20個超え始めるときつい● テストスイートがない
● 回帰テストをまとめて一発でやりたい● 汚れたグローバル環境の再浄化が要る
● 実行スコープがグローバル● 連続して複数のテストを行うと前の状態に依存する● 続けてやったら単体テスト失敗するなどもってのほか
というわけで作ったもの● Lime単体用のテストスイート● 複数のLimeテストをまとめて実行できる● 個々のテストは個別のPHPインタプリタで実行されるようにする
● OKの場合はいちいち報告しない
ソースはこれだけ
使い方
実行した様子/Users/tanakahisateru/Sites/pinoco/test/unit/test_vars.php:# Pinoco_Vars Test# toArray test# import test1..47# Looks like everything went fine.
/Users/tanakahisateru/Sites/pinoco/test/unit/test_list.php:# Pinoco_List Test# push/pop# shift/unshift1..60# Looks like everything went fine.
/Users/tanakahisateru/Sites/pinoco/test/unit/test_lazy.php:# Pinoco_LazyValueProxy Test(略)
それでも残る課題● 外部のシステムとの関係
● =単体テストにある本質的な問題● 言語ロジック外のシステムに依存すると、純粋な単体テストではなくなる。
● データ保存系のテストは次のテストの環境を書き換えてしまう。
● けれど自分の実装は実行の前提となる環境がないと動かせない、こまった。
単体テストで重要な技術● モックオブジェクト
● 本番用と同じインターフェースの偽オブジェクト● オブジェクト指向的にいうとポリモーフィズム● 実際は何も書き換えないバージョンの外部環境● テスト可能な設計とは、モックオブジェクトを受け入れられるインターフェースだということ
● はいオブジェクト指向きましたね
まあこういうことですよ
Symfony2でまさかの乗り換え… orz
でも、
PHP is not for OOP coders only.
そこまで突き詰めなくてもまずはTDDしよう。ユーザ層の幅広さ、それが「PHPらしさ」でしょ。
詳しくはGitHubのPinocoのソースにて。
ありがとうございました。