序文
僕の身の回りでは単体テストのコードが書かれていないコードベースをよく見かけます。そしてそのようなコードベースはリファクタリングを拒絶し、またリリースに大変な労力を要する巨大な怪物と成り果てていきます。
納期や面倒くささに圧され怪物が日々生み出されていますが、健全で継続的なソフトウェア開発にとって単体テストの自動化は非常に強力なツールです。
TDD(テスト駆動開発)に代表されるようにテストファーストという考え方は非常に多くの恩恵をもたらします。リファクタリングの信頼性を高め、ソフトウェアの発展を望む殊勝な開発者のための精神安定剤となり得ます。
テストコードのないプロジェクトは多くの場合、参加している開発者が「テストを書くべきだとは聞くけれど、テストコードを書いたことがない。書き方がわからない。そもそもよくわからない。」という霧中にいます。
しかし、その霧は実はメガネの曇りです。拭き取ればすぐに視界良好になるのだとわかります。テストコードは手の届くところにあるのだとわかります。
モダンな言語やフレームワークであればテストフレームワークは大抵の場合用意されています。自分が利用しているものについて調べてみてください。今回はPHPについて単体テストとリファクタリングについて見ていきます。
テスト対象
まず、以下の要件を満たす関数を考えます。
文字列の後ろ3文字がlish
で終わっているかどうかを判定できる。
例えば正規表現による文字列マッチを使って実装してみます。
<?php
/*
* 文字列が"lish"で終わっているかをチェックする。
*
* @param string str 対象の文字列
* @return bool
*/
function is_end_with_lish($target_str) {
return preg_match('/lish$/', $target_str) === 1;
}
テストを書く
この関数に対するテストは、例えば簡単なものとして以下のものが思いつきます。
# | テスト項目 | 入力値 | 結果 |
---|---|---|---|
1 | lishで終わる | english | true |
2 | lishで終わらない | good | false |
3 | 4文字以下 | ish | false |
4 | 大文字のLISHで終わる | SINGLISH | false |
5 | マルチバイト文字 | english | false |
6 | 空文字 | '' | false |
7 | null | null | false |
まずは#1のテストに対応するテストコードを書いてみます。
<?php
include("./func.php");
// #1
echo (is_end_with_lish("english")) ? "success." : "failed.";
これだけでテストは自動化されました。
このコードをターミナルで実行すると即座にテストを実行できます。
他のテストも追加します。
<?php
include("./func.php");
// #1
echo (is_end_with_lish("english")) ? "success." : "failed.";
// #2
echo (!is_end_with_lish("good")) ? "success." : "failed.";
// #3
echo (!is_end_with_lish("ish")) ? "success." : "failed.";
// #4
echo (!is_end_with_lish("SINGLISH")) ? "success." : "failed.";
// #5
echo (!is_end_with_lish("english")) ? "success." : "failed.";
// #6
echo (!is_end_with_lish("")) ? "success." : "failed.";
// #7
echo (!is_end_with_lish(null)) ? "success." : "failed.";
これを実行します。
php test.php
success.
が7つ並んだ結果が得られるかと思います。
リファクタリングをする
ここでふと気づきます。今回のケースでは正規表現よりも文字列の切り出し(substr
等)のほうが実行効率がいいのではないだろうか。
割愛しますが実際に調べてみるとsubstrを利用した実装の方が処理速度が1.5倍ほど早いです。
is_end_with_lish
関数を変更してみます。リファクタリングを行うわけです。
<?php
/*
* 文字列が"lish"で終わっているかをチェックする。
*
* @param string str 対象の文字列
* @return bool
*/
function is_end_with_lish($target_str) {
return substr($target_str, -4) === "lish";
}
変更した状態で再度テストを実行してみます。
すると先ほどと同様にすべてsuccess.
となるかと思います。
このことから、リファクタリング後にも単体テストがカバーしている範囲に限りですが
挙動に変更がないことがわかります。
このようにテストを書くことは仕様を考えることを強制し、またリファクタリングを自信をもって行うための強力なツールになりえます。
帰結
フレームワークを使わなくてはいけないということではありませんが、PHPではPHPUnitやPHPSpecなどの強力なテストフレームワークが存在します。他の言語もほとんどの場合フレームワークが存在しています。これらを利用するのがベターでしょう。
しかし敷居が高いと感じるのならば、自分でまずは書いてみるのも一つの手です。
とにかくここで伝えたかったのはテストを書くのは特殊な技能を要するものでもなんでもなく、誰もが今日から始められるということです。