そのため、cmocka を使用して既存のコードの単体テストを開発している問題を再現するおもちゃのプログラムです。問題は、ネストされた関数呼び出しがモックされないことです。これにより、単体テストは、ネストされた関数呼び出しが適切に実行されることに依存します。元のコードには「内部関数呼び出し」として存在する静的関数があるため、「mockable_static」定義が使用されましたが、単体テストの目的で、これらは外部呼び出しに対して開かれていることに注意してください。(このアイデアが生まれたスタックオーバーフローの投稿を参照してください)
コードは次のとおりです。
func.h:
#ifndef FUNC_H_
#define FUNC_H_
#ifdef UNIT_TESTING
#define mockable_static
mockable_static char* bar();
#endif
char* foo();
#endif // FUNC_H_
func.c:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifndef UNIT_TESTING
#define mockable_static static
#else
#define mockable_static
#endif
mockable_static char* bar (){
printf("This is bar!\n");
char *str = "This is the result of bar!";
return str;
}
char* foo(){
printf("This is foo, and it should return the results of bar()\n");
char * res;
res = bar();
return res;
}
test.c:
#include <setjmp.h> /* needs to be before cmocka.h */
#include <stdio.h>
#include <string.h>
#include <cmocka.h>
#include "func.h"
static void test_bar(void **state);
static void test_wrap_bar(void **state);
static void test_foo(void **state);
char* __real_bar();
char* __wrap_bar(){
printf("This is a wrap and doesn't return bar!\n");
return (char*)mock();
}
int main(void){
//bar test
const struct CMUnitTest bar_tests[] = {
cmocka_unit_test(test_bar),
cmocka_unit_test(test_wrap_bar)
};
const struct CMUnitTest foo_tests[] = {
cmocka_unit_test(test_foo)
};
//foo test w/ mocking bar
int status;
status = cmocka_run_group_tests(bar_tests,NULL,NULL);
status = cmocka_run_group_tests(foo_tests,NULL,NULL);
printf("Status = %d\n",status);
return status;
}
static void test_bar(void **state){
char expected_res[] = "This is the result of bar!";
char * actual_res;
actual_res = __real_bar();
assert_string_equal(actual_res,expected_res);
}
static void test_wrap_bar(void **state){
char * this = "I don't want bar!";
will_return(__wrap_bar,this);
char * res = bar();
assert_string_equal(res,this);
}
static void test_foo(void **state){
char * this = "I don't want bar!";
will_return(__wrap_bar,this);
char * res = foo();
assert_string_equal(res,this);
}
gcc コンパイル行:
gcc ./test.c ./func.c -DUNIT_TESTING -g -Wl,--wrap=bar -o test -lcmocka-static
テスト実行の結果:
[==========] Running 2 test(s).
[ RUN ] test_bar
This is bar!
[ OK ] test_bar
[ RUN ] test_wrap_bar
This is a wrap and doesn't return bar!
[ OK ] test_wrap_bar
[==========] 2 test(s) run.
[ PASSED ] 2 test(s).
[==========] Running 1 test(s).
[ RUN ] test_foo
This is foo, and it should return the results of bar()
This is bar!
[ ERROR ] --- "This is the result of bar!" != "I don't want bar!"
[ LINE ] --- ./test.c:59: error: Failure!
[ FAILED ] test_foo
[==========] 1 test(s) run.
[ PASSED ] 0 test(s).
[ FAILED ] 1 test(s), listed below:
[ FAILED ] test_foo
1 FAILED TEST(S)
Status = 1
ご覧のとおり、bar() は foo() にラップされませんが、ラップ テストでは、foo() が bar を呼び出すのとまったく同じように bar がラップされます。Bar は、cmocka テスト ライブラリの一部である __real_bar() を使用してテストされます (__real_bar() にはプロトタイプがありますが、関数は定義されておらず、cmocka のドキュメントに従って期待される結果を返します。ネストされた関数呼び出しで単体テストを使用した経験がある人なら誰でも)ネストされた関数呼び出しを cmocka でモックした結果は見つかりませんでしたが、google-foo が不足している可能性があります. test_foo() の最後で assert が削除されると、will_return キュー内の未使用の値のためにテストが失敗します.
[==========] Running 2 test(s).
[ RUN ] test_bar
This is bar!
[ OK ] test_bar
[ RUN ] test_wrap_bar
This is a wrap and doesn't return bar!
[ OK ] test_wrap_bar
[==========] 2 test(s) run.
[ PASSED ] 2 test(s).
[==========] Running 1 test(s).
[ RUN ] test_foo
This is foo, and it should return the results of bar()
This is bar!
[ ERROR ] --- %s() has remaining non-returned values.
./test.c:56: note: remaining item was declared here
[ FAILED ] test_foo
[==========] 1 test(s) run.
[ PASSED ] 0 test(s).
[ FAILED ] 1 test(s), listed below:
[ FAILED ] test_foo
1 FAILED TEST(S)
Status = 1