不正パラメータのテスト、assert実装方法について
テストでは、正常系処理のテストだけでなく、不正パラメータの処理に対してのテストもすべきです。
不正パラメータの処理に対してのテストとは、例えば以下のような関数を実装した場合、
/**
* 与えられた値の平方根を返します。
* valueが負の値のときデバッグ版ではassertで停止します。リリース版では0を返します。
*
* @param value 値
* @return valueの平方根
*/
double getSqrt(double value)
{
// もちろん引数チェック。負の値は平方根計算できないので。。。
if (value < 0) {
assert(0 && "illegal argument");
return 0.0;
}
return sqrt(value); // sqrt()も引数チェックしてくれますが、今回は無視してください。
}
この関数のテストを実施する場合、value = 9.0の正常系処理のほかに
value = -9.0などの不正な値を渡してみるような不正系処理のテストも必要です。
しかし、その実施には問題点があります。
リリース版ではvalue = -9.0が渡されたら0を返す仕様ですが(assertは無効にしてリリースするので)、
テストの段階(デバッグ版)では、まだassertが有効状態でありシステムが停止してしまうので、0が返されるのが確認出来ません。
それを解決するには標準ライブラリのassert()を使うのではなく、
独自にassert()を作成する必要があります。※1
そのassertに求められる仕様は、
1.通常の正常系テストでは、システムを停止させる。(標準assertの振る舞い)
2.不正系テストでは、システムを停止させない。しかし、assertが呼ばれたことを確認できる。
3.リリース版では、assertを無効にする。
4.標準ライブラリassertと「シグニチャと戻り値の型」を変えない。(=関数の記述を変えない)
です。
assert()の実装例を以下にあげます。
<<assert.h>>
#include <stdbool.h>
void enableStop();
void disableStop();
extern void my_assert(const char *expr, const char *file, const int line);
bool wasAssertionCalled();
#define assert(expr) *1
<<assert.c>>
#include "assert.h"
#include <stdio.h>
#include <limits.h>
static bool enable = true;
static bool called = false;
void enableAssertion(){ enable = true };
void disableAssertion(){ enable = false};
void my_assert(const char *expr, const char *file, const int line)
{
called = true;
if (enable) {
printf("%s : %d 【%s】", file, line, expr);
sleep(UINT_MAX); // システムの停止状態にする関数、他にあったっけ?(´・ω・`)
}
}
bool wasAssertionCalled(){ return called };
独自実装のassertを適用することで、不正処理系のテストも可能となりました。
以下がテストコードの例です。
<<test.c>>
// 正常系のテスト
void test_normal(void)
{
// prepare
const double VALUE = 9.0;
// action
const double RESULT = getSqrt(VALUE);
// check
/* CU_ASSERTは単体テストフレームワークCUnitの書き方です。無視してください。*/
CU_ASSERT(RESULT == 3.0);
}
// 不正系のテスト
void test_abnormal(void)
{
// prepare
disableAssertion(); /* assertでシステムが停止しないようにする */
const double VALUE = -9.0;
// action
const double RESULT = getSqrt(VALUE); /* 標準assertではシステムが停止して絶対に先に進めない */
// check
CU_ASSERT(wasAssertionCalled() == true); /* assertが呼ばれたことを確認 */
CU_ASSERT(RESULT == 0.0); /* 不正の値のとき0が返ってるはず。(リリース版の処理を再現)*/
enableAssertion(); /* 他のテストのために、 assertでシステムが停止するようにする*/
}
不正系処理もテストして、カバレッジ100%を目指しましょー(・∀・)
---------------------------------------------------------------------------
豆知識
※1.
assertを別途、自分の都合にあった実装するソフトウェア開発者は多いらしいです。
今回の例の他に、例えばassertの文字列出力を変更するなどです。
標準assertはファイル名を表示するので、ログ取得するとき長いファイル名(パス名含む)は、
メモリが勿体ないです。
なので、ファイル名ごとに番号をふって、assertではその番号を表示したりします。