ユニットテストについて
能書き
「ユニットテスト」について調べると・・・「重要」「PHPUnitを使う」という情報が多々でてきたんだけど、もう少し概念というか・・・「何をやって、どうなればいいの?」ってコトの説明が無くて困った。
知ってる人たちって・・・
知ってる人たちの説明って・・・偏見かもしれないけどプログラマー含めて、PC関連が詳しい人たちの説明って自分が初心者のときのコトを忘れているような気がする。
「カレーライス」を知らない人間に「カシミールカレーが美味しいよ!」って言っても「そもそもカレーが分からないんだけど?」ってなると思うんだ。
さらに「カレーライス」を知らない人間に「辛くて、黄色くてドロっとしててご飯にかけて食べるんだ!」って言っても想像しづらいと思うんだ。
まずは「カレーライス」を知らない人間には「カレーライスを食べさせる」ってのが重要だと思うんだ。
でもプログラマーな人たちってそういう説明をしてくれないから困る。
「初心者向け」って上級者向け?
さらに「初心者向け~」ってヤツはサンプルが理解できない。
理解できないサンプルでよくあるパターンが「ニンジンをサイの目にする」を「包丁」じゃなくて「斧」でやりやがる。
知ってる人は「包丁も斧も刃物」としてそのサンプルを読み解くんだろうけど・・・コッチとしては「何故、斧を使うの?」という根本的な部分が理解できない。
じゃぁ「ユニットテストについて何が知りたいの?」って聞かれると・・・単純なモノを使って「どうなるのか?」「どうなればいいのか?」「その後、どうしたらいいのか?」ってコトを教えて欲しい。
とりあえずアレコレ調べて、ユニットテストについて何となく分かったような気がしたので・・・まとめてみた。
簡易サンプルと解説
下記のサンプルで使っている「$t」ってのが「ユニットテスト」を簡略化するためのオブジェクト(lime.phpってヤツ)みたいな感じで・・・これのおかげでなんとなく分かったような気がする。
▼サンプルソースで使われている関数の挙動。
$t->is(a, b, c):「aとbが等しければOK。cは今回の作業内容のメッセージ。」
▼サンプルソース
$t->is(strtolower(‘FOO’), ‘foo’, ‘strtolower() は入力を小文字に変換する’);
$t->is(strtolower(‘foo’), ‘foo’, ‘strtolower() は小文字を変更しない’);
$t->is(strtolower(’12#?@~’), ’12#?@~’, ‘strtolower() はアルファベットではない文字を変更しない’);
$t->is(strtolower(‘FOO BAR’), ‘foo bar’, ‘strtolower() は空白をそのままにする’);
$t->is(strtolower(‘FoO bAr’), ‘foo bar’, ‘strtolower() は混合する文字の入力を扱う’);
$t->is(strtolower(""), ‘foo’, ‘strtolower() は空の文字列を foo に変換する’);
- $t->isa_ok(strtolower(‘Foo’), ‘string’, ‘strtolower() は文字列を返す’);
- →「strtolower(‘Foo’)」の結果が「型:string」であるかテスト
- $t->is(strtolower(‘FOO’), ‘foo’, ‘strtolower() は入力を小文字に変換する’);
- 「strtolower(‘FOO’)」の結果が「foo」になるかテスト
- $t->is(strtolower(‘foo’), ‘foo’, ‘strtolower() は小文字を変更しない’);
- 「strtolower(‘foo’)」の結果が「foo」になるかテスト
- $t->is(strtolower(’12#?@~’), ’12#?@~’, ‘strtolower() はアルファベットではない文字を変更しない’);
- 「strtolower(’12#?@~’)」の結果が「12#?@~」になるかテスト
- $t->is(strtolower(‘FOO BAR’), ‘foo bar’, ‘strtolower() は空白をそのままにする’);
- 「strtolower(‘FOO BAR’)」の結果が「foo bar」になるかテスト
- $t->is(strtolower(‘FoO bAr’), ‘foo bar’, ‘strtolower() は混合する文字の入力を扱う’);
- 「strtolower(‘FoO bAr’)」の結果が「foo bar」になるかテスト
- $t->is(strtolower(""), ‘foo’, ‘strtolower() は空の文字列を foo に変換する’);
- 「strtolower("")」の結果が「foo bar」になるかテスト
▼実行結果
ok 2 – strtolower() は入力を小文字に変換する
ok 3 – strtolower() は小文字を変更しない
ok 4 – strtolower() はアルファベットではない文字を変更しない
ok 5 – strtolower() は空白をそのままにする
ok 6 – strtolower() は混合する文字の入力を扱う
not ok 7 – strtolower() は空の文字列をfooに変換する
lime.php
「lime.php」ってのをダウンロードして、実際に動かしてみた。
「lime.phpを使ってユニットテスト」している記事からリンクをたどっていったんだけど・・・古くてエラー・ワーニングが出まくる。
「じゃぁ最新版のlime.phpをダウンロードしよう!」と思ったけどドコにいるのかが分からない。
調べるとそもそも「何かのフレームワークのユニットテスト用にlime.phpを使っている」というコトらしい。
で、その「何かのフレームワーク」の最新版を見ていたけど「lime.php」じゃなくて「phpUnit」ってのが見つかった。
ダウンロードするのも面倒だし、ユニットテストを理解するのに「何かのフレームワーク」自体の使いかたを覚えるのも面倒だったので諦めた。
自分でやってみる。
「lime.php」で実際に試せなかったってのは、いいとして・・・上記のサンプルと結果からやっていることはわかった・・・と思う。
「どうなるのか?」「どうなればいいのか?」「その後、どうしたらいいのか?」ってコトを知るためにも試しに自分でやってみよう。
1回目
まずは単純なプログラムを作ってみる。
数字(or 数値)を入れたら値引き後の価格にして返してくれるってヤツ。
▼テストしたいプログラム
return $price – 100;
}
echo ‘値引き後:’ . priceDown(350) . ‘円’;
▼テストする内容(priceDown()の仕様?)
・引数が数字以外の文字のときは、戻値は「0」。
・引数が空のときは、戻値は「-1」。
・引数が無いときは、戻値は「-1」。
・引数がその他のときは、戻値は「-1」。
※「引数がその他」ってのは「null」のとき。
▼テストで使うプログラム
echo gettype($val);
if ( gettype($val) == ‘integer’) {
return ‘ok’;
}
return ‘ng’;
}
function chkZro($val="") {
if ( $val == 0) {
return ‘ok’;
}
return ‘ng’;
}
function chkEmpty($val="") {
if ( $val == -1) {
return ‘ok’;
}
return ‘ng’;
}
function chkNull($val="") {
if ( $val == -1) {
return ‘ok’;
}
return ‘ng’;
}
$chkArg1 = "5000";
$chkArg2 = 300;
$chkArg3 = ‘丸ごとシマウマ’;
$chkArg4 = "";
echo ‘戻値はint型(数字のとき):’ . chkInt(priceDown($chkArg1)) . "\n";
echo ‘戻値はint型(数値のとき):’ . chkInt(priceDown($chkArg2)) . "\n";
echo ‘戻値はint型(文字のとき):’ . chkInt(priceDown($chkArg3)) . "\n";
echo ‘戻値はint型(空のとき):’ . chkInt(priceDown($chkArg4)) . "\n";
echo ‘引数が数字以外の文字のとき、戻値は「0」:’ . chkZro(priceDown($chkArg3)) . "\n";
echo ‘引数が空のときは、戻値は「-1」:’. chkEmpty(priceDown($chkArg4)) . "\n";
echo ‘引数がその他、戻値は「-1」(無し):’. chkNull(priceDown()) . "\n";
echo ‘引数がその他、戻値は「-1」(null):’. chkNull(priceDown(null)) . "\n";
▼実行結果
戻値はint型(数値のとき):ok
戻値はint型(文字のとき):ok
戻値はint型(空のとき):ok
引数が数字以外の文字のとき、戻値は「0」:ng
引数が空のときは、戻値は「-1」:ng
Warning: Missing argument 1 for priceDown(), called in ○○○
Notice: Undefined variable: price in ○○○
引数がその他、戻値は「-1」(無し):ng
引数がその他、戻値は「-1」(null):ng
この実行結果はユニットテストの結果以前の問題。
「chkNull(priceDown())」で「引数が必要な関数(priceDown())なのに引数が無いよ!」って怒られたので「priceDown()」を修正。
2回目
「priceDown()」で引数が無くてもOKにする。
▼テストしたいプログラム
return $price – 100;
}
echo ‘値引き:’ . priceDown(350) . ‘円’;
▼実行結果
戻値はint型(数値のとき):ok
戻値はint型(文字のとき):ok
戻値はint型(空のとき):ok
引数が数字以外の文字のとき、戻値は「0」:ng
引数が空のときは、戻値は「-1」:ng
引数がその他、戻値は「-1」(無し):ng
引数がその他、戻値は「-1」(null):ng
「引数が○○○の時~」がすべて「ng」となっているので「priceDown()」を修正。
3回目
「priceDown()」で引数が空・その他(無し、null)のとき「-1」を返すように修正。
▼テストしたいプログラム
if ( $price == "" || $price == null ) {
return -1;
}
else {
return $price – 100;
}
}
echo ‘値引き後:’ . priceDown(350) . ‘円’;
▼実行結果
戻値はint型(数値のとき):ok
戻値はint型(文字のとき):ok
戻値はint型(空のとき):ok
引数が数字以外の文字のとき、戻値は「0」:ng
引数が空のときは、戻値は「-1」:ok
引数がその他、戻値は「-1」(無し):ok
引数がその他、戻値は「-1」(null):ok
「引数が数字以外の文字のとき、戻値は「0」」の時が「ng」となっているので「priceDown()」を修正。
4回目
引数が数字以外の文字のとき、戻値が「0」になるように修正。
▼テストしたいプログラム
if ( !is_numeric($price) ) {
return 0;
}
if ( $price == "" || $price == null ) {
return -1;
}
else {
return $price – 100;
}
}
echo ‘値引き後:’ . priceDown(350) . ‘円’;
▼実行結果
戻値はint型(数値のとき):ok
戻値はint型(文字のとき):ok
戻値はint型(空のとき):ok
引数が数字以外の文字のとき、戻値は「0」:ok
引数が空のときは、戻値は「-1」:ng
引数がその他、戻値は「-1」(無し):ng
引数がその他、戻値は「-1」(null):ng
今回の修正だと引数が空、その他のときが「ng」となってしまうので「priceDown()」を修正。
5回目
引数が「数字以外の文字のときは0を返し」「引数が空、その他のときは-1を返す」ように修正。
▼テストしたいプログラム
if ( !is_numeric($price) ) {
if ( $price == "" || $price == null ) {
return -1;
}
return 0;
}
return $price – 100;
}
echo ‘値引き後:’ . priceDown(350) . ‘円’;
▼実行結果
戻値はint型(数値のとき):ok
戻値はint型(文字のとき):ok
戻値はint型(空のとき):ok
引数が数字以外の文字のとき、戻値は「0」:ok
引数が空のときは、戻値は「-1」:ok
引数がその他、戻値は「-1」(無し):ok
引数がその他、戻値は「-1」(null):ok
これでやっと希望通り動くことが分かったので「OK!」となる。
priceDown()を使っているうちに新たに「○○○のときは○○○を返す」っていうのが増えたりしたら、それを追加していかなくちゃいけないっぽい。
ユニットテストのまとめ
ひとまずユニットテストはある程度、理解した。
「どうなるのか?」「どうなればいいのか?」ってのは上記の「実行結果」ってヤツだな。
「その後、どうしたらいいのか?」ってのは「実行結果」が「すべてok」となるように「テストしたいプログラム」を修正すればいいんだ。
ココまで分かればあとは「テストで使うプログラム」ってのを「毎回作るのが面倒だ! 何か専用のツールみたいなのが欲しいよ~」となって、そこで「PHPUnit」ってヤツが出てくるわけだ。