テスト駆動を初めて実施してみた
テスト駆動とは、先にテストケースを用意してから実装を開始する方法です。
コードはいつか必ずテストするので理想的ではあるのですが、期限が迫っていたりここまでに入力値は保証されている場合もあるので、なかなか実践できませんでした。
通っているスクールで規模の小さい実装があり、試してみることにしました。
標準関数の再実装
strXXXやmemXXXなど、標準関数を再実装する課題があり、テスト駆動で始めました。 段取りは以下の手順で進めました。
- 仕様を理解する
- テストケース・テスト関数を書く
- 想定内の値
- 入力範囲の限界値付近
- ポインタはNULLの場合も
- 文字数字、可読でない値(\nとか)、ascii外の値(0x80〜)
- 考えつく組み合わせ
- 空関数を用意してコンパイル、ビルドが通る状態にする。
- テストを実施する。(ここで全部NGになって良い。)
- 実装しながらテスト
2回以上繰り返す処理はマクロor関数化する
例えば、atoiのように、入力値に応じて値を返す関数は他にもあります。(isalpha, isdigitとか)
2回以上繰り返す処理はマクロ化か関数化を考えます。
マクロ化の例
<実装> #define JUDGE(name, val) \ printf("[%s test]\t", # name); \ if (name(val) != ft_ ## name(val)) {\ printf("NG - %s: %d,%d\n", # name, name(val), ft_ ## name(val));\ }\ else {\ printf("OK - %s\n", # name);\ } int main(void) { JUDGE(atoi, " 123"); JUDGE(atoi, "-123"); JUDGE(atoi, "1234567890"); return (0); } <出力結果> [atoi test] OK - atoi [atoi test] OK - atoi [atoi test] OK - atoi
マクロは、値の置き換えに限らず、# name は、"名前" としてダブルコーテーションをつけた値として扱われます。
そして、ft_ ## name(val) は、連結した ft_atoi(val) として動作させることができます。
テスト駆動の良かったこと
関数の実装量はかなり増えるので面倒なのですが、良い点もありました。
- リファクタリングなどちょっとした修正でも確認ができるので安心感がある
- テストした経過を残せる
- 仕様の理解が深まった状態で実装に入れる
(テストを書くために仕様を理解していないといけないので)
これが本質的なテスト駆動のメリットですが、
- 全てNGのテストが徐々にOKになっていくのが気持ち良い
- 結果表示のやり方にこだわると綺麗に見せられる
のように、モチベーションに関わる利点もありました。
業務ではどうしても大きな成果を期待されがちですが、こうした一つ一つの関数が堅牢であること、手直しをしても安全を保証できることはかなり大事だなと体感することができました。
テストすごい。