今日は社外WGに参加。春の嵐だ!
==
僕はC言語ばっかの開発をやってるので、ここでもC言語をベースにしていろんなことを書きます。あしからず。
リリース後の欠陥発覚は手戻り工数が最もかかるものだが、その多くは単体テストで発見可能なものが多い。某静的解析ツールのベンダー資料によるとその割合は40%近くになるらしい。定量的な資料は持っていないが、僕も単体テストの質がシステム全体やプロジェクト全体の質を大きく左右すると考えている。
そこでだ。僕の考える、上質な単体テスト for C言語はこんな感じ。
テストしやすいという、テスタビリティ
(例1)
int
func1(
int a1,
char* a2,
int a2size )
(例2)
foor_obj_t*
func2(
var_obj_t* b1 )
まず、基本的な関数の型はint型がいろいろ都合がいい気がする。エラー番号を復帰したり、errnoを復帰したり、直感的なものがよいと思う。例1のように、文字列格納先の先頭ポインタを引数渡しする場合は、呼び元で確保したサイズも合わせて引数渡しすること。(ちなみによくあるライブラリによっては文字列の先頭ポインタとサイズを構造体にしているものも多い。)サイズを引数指定することで、データコピー時のバッファチェックがしやすくなる。
例2のような引数や復帰値に構造体を使った関数は、関数内で動的確保を行い、呼び元の責任で開放する。構造体の初期化がしやすくなる(と、僕は思ってる)。
テストケースを作成しやすいという、テスタビリティ
例3
int
func3(
int c1,
const char* c2,
char* c3,
int c3size )
{
/* (A)引数チェック */
if( c1 <= 0 || c2 == NULL || *c2 == 0x00 || c3 == NULL || c3size <= 0 ){
return(ERR_ARGV);
}
/* (B)本体処理 */
/* (C)エラー処理 */
retired:
/* 事後処理・初期化・開放処理 */
func_free();
return(ERR_SYSTEM);
}
実装は(A)引数チェック、(B)本体処理、(C)エラー処理の3部で構成すると、テストケースが作成しやすいのではないかと思っている。(A)引数チェックは必須。ここでのチェックがこの先のテスト空間を限定してくれるから。あるいは、ここに欠陥を紛れ込ませて、テストのテスト(テストが無作為で紛れ込ませた欠陥を検出できるかの検証)ができるかも?引数によっては関数の旅をするものもいるだろうが、
僕の場合はすべての関数に対してここは必ずテストケースとして設定する。
(B)実装部分については、割愛。
(C)C言語で「goto文は使わない」というしきたりがあるかもしれないが、エラー処理については使ったほうがフローが簡潔になるし、開放漏れ・クローズ漏れを起こしにくいんじゃないかと思います。開放漏れ・クローズ漏れはなかなか単体テストでは発見しずらいか。
てな感じで僕は設計してます。
==
さあ、実装とテストを駆動させるのだが、ご紹介するのは、僕が単体テストで使う3つのツール。
- minunit(自作改良版) - 単体テスト用テストコマンド自動生成
minunitというたった3行のユニットテストツール。これをベースにテストコマンド用ソースを自動生成するツールを自作して利用。
- gcov - プログラムコードカバレッジ測定ツール
GCCのプロファイリング機能で「-fprofile-arcs -ftest-coverage」を指定してコンパイルする。詳しくはここで。分岐網羅の測定も可能か。
- lcov - テストカバレッジ情報グラフィック化
gcovで測定した結果をHTMLと画像に変換するツール。詳しくはここ。Linux用だが、Solarisにも移植はできるよ。
これらのツールについては、もう少し勉強・改良してみたいと思っています。
最近のコメント