私に役立つ解決策を見つけました。おそらくあなたにも役立つでしょう。
MACROS を単独で使用しても、これまでのところしか得られません。関数でテストを実行したいが、MACROS を使用してさまざまな方法でスタブ化する場合、コードを何度か再構築して各条件を個別に実行する必要があります。これは自動化が難しいため、さまざまなシンボルを定義し、コードを再構築して結果を集計するバッチ スクリプトが必要です。
ただし、MACROS を使用して、スタブ化する各関数の関数ポインターを定義する場合は、テストするターゲット コードにいくつかのマイナーな変更を加えることができると仮定すると、実行可能なソリューションが得られます。
次の例は、次の影響を強く受けています。
- http://eradman.com/posts/tdd-in-c.html
- http://locklessinc.com/articles/mocking/
- http://www.embedded.com/design/programming-languages-and-tools/4007177/2/Doing-C-code-unit-testing-on-a-shoestring-Part-1-The-basics-and-道具
この例では、MUT はモジュール アンダー テストを表します。
mut.h、mut.c、test_mut.h、test_mut.c の 4 つのファイルがあるとします。また、これをビルドするときにシンボル UNIT_TEST を定義できると仮定しましょう。
mut.h には、公的にアクセス可能なすべての関数が含まれます。この例では何もないので、忘れておきましょう。
それでは、mut.c のバージョンから始めましょう。
#include <cstdbool>
#include "mut.h"
static bool foo(int baz);
static bool bar(int baz);
static bool foo(int baz)
{
bool ret = false;
if(bar(baz))
{
//do something
ret = true;
}
else
{
ret = false;
}
return ret;
}
static bool bar(int baz)
{
//Black box mystery / Don't care
}
すでに単体テスト済みの bar があるとしましょう。それは正常に動作します。ここで、foo をテストしたいのですが、bar を適切に実行するために必要なすべてをセットアップする気はありません。そのため、バーをスタブアウトする必要があります。
それでは、新しいヘッダー test_mut.h を含めましょう。とりわけ、test_mut.h には次のようなものがあります。
#ifdef UNIT_TEST
...
//Convert every static definition into an extern declaration.
#define static extern
bool bar_mock_true (int baz);
bool bar_mock_false(int baz);
bool bar_real (int baz);
extern bool(*bar_ptr)(int baz);
#define bar bar_ptr
...
#endif
ご覧のとおり、スタブ/モックまたは実際のバー関数を指すことができる新しい関数ポインターを定義しました。このヘッダーは test_mut.c にも含まれるため、スタブ化された関数を test_mut.c 内で定義できるようになりました。mut.c 内にある必要はありません。
mut.c を少し変更する必要があります。
mut.c は "test_mut.h" をインクルードする必要があり、単体テスト中は bar() の標準宣言を無効にする必要があり、関数の定義を bar_real() に変更する必要があります。
...
#include "test_mut.h"
...
#ifdef UNIT_TEST
static bool bar(int baz);
#endif
...
#ifdef UNIT_TEST
static bool bar_real(int baz)
#else
static bool bar(int baz)
#endif
{
//Black box mystery / Don't care
}
スタブ化する必要があるすべての関数には、同様の #ifdef と、宣言と定義の周りの名前変更が必要です。したがって、テスト対象のコードは、残念ながら少し雑然とする必要があります。
これで、test_mut.c は次のようにコードを実行できるようになりました。
#include <cstdbool>
#include "test_mut.h"
...
UT_STATIC void test_foo(void)
{
int baz = 0;
extern bool foo(int baz);
//Test Case A
bar_ptr = bar_mock_true;
TEST_ASSERT(foo(baz), "Condition A");
//Test Case B
bar_ptr = bar_mock_false;
TEST_ASSERT(!foo(baz), "Condition B");
}
bool bar_mock_true(int baz)
{
return true;
}
bool bar_mock_false(int baz)
{
return false;
}
以下は、私のソース ファイルの完全なリストです。ここで軽量のテスト ハーネスを作成しました。コンパイルして IAR 組み込みワークベンチ コンパイラで実行し (他では試していません)、次の出力を生成します。
.. テスト実行: 2
mut.c
#include <cstdbool>
#include "mut.h"
#include "test_mut.h"
static bool foo(int baz);
#ifndef UNIT_TEST
static bool bar(int baz);
#endif
static bool foo(int baz)
{
bool ret = false;
if(bar(baz))
{
//do something
ret = true;
}
else
{
ret = false;
}
return ret;
}
#ifdef UNIT_TEST
static bool bar_real(int baz)
#else
static bool bar(int baz)
#endif
{
//Black box mystery / Don't care
}
test_mut.h
#ifdef UNIT_TEST
#ifndef _TEST_MUT_H
#define _TEST_MUT_H
//Handle to your test runner
void test_mut(void);
//Track the number of test cases that have executed
extern int tests_run;
//Convert every static definitions into extern declarations.
#define static extern
//An alternate definition of static for the test barness to use
#define UT_STATIC static
bool bar_mock_true (int baz);
bool bar_mock_false (int baz);
bool bar_real (int baz);
extern bool(*bar_ptr)(int baz);
#define bar bar_ptr
//Test Macros
#define TEST_FAIL(name) \
do \
{ \
printf("\nTest \"%s\" failed in %s() line %d\n", (name), __func__, __LINE__); \
} while(0)
#define TEST_ASSERT(test_true,test_name) \
do \
{ \
tests_run++; \
if(!(test_true)) \
{ \
TEST_FAIL(test_name); \
} \
else \
{ \
printf("."); \
} \
} while(0)
//... Insert any other macro instrumentation you may need...
#endif // _TEST_MUT_H
#endif // UNIT_TEST
test_mut.c
#ifdef UNIT_TEST
#include <cstdbool>
#include <cstdio>
#include "test_mut.h"
#include "mut.h"
UT_STATIC void test_foo(void);
int tests_run = 0;
inline UT_STATIC void test_report(void);
void test_mut(void) {
//call your setup function(s)
test_foo();
//call your teardown function(s)
test_report();
}
inline UT_STATIC void test_report(void)
{
printf("\nTests Run: %d\n", tests_run);
}
void main(void)
{
test_mut();
}
//Setup the function pointer for bar, by default it will point to the real
//bar function, and not a stub.
bool(*bar_ptr)(int baz) = bar_real;
UT_STATIC void test_foo(void)
{
int baz = 0;
extern bool foo(int baz);
//Test Case A
bar_ptr = bar_mock_true;
TEST_ASSERT(foo(baz), "Condition A");
//Test Case B
bar_ptr = bar_mock_false;
TEST_ASSERT(!foo(baz), "Condition B");
}
bool bar_mock_true(int baz)
{
return true;
}
bool bar_mock_false(int baz)
{
return false;
}
#endif