main関数をテストしたい基地外な人のためのメモ
やりたいこと
- 稀有に発生するかもしれないエラーをテストしたい
- ロジックは、main関数に直書きされてる
- 元のソースコード/プロジェクトは弄ってはいけない!
例えば、次のようなコードをテストしたいとします。
// main.c #include <stdio.h> #include "hoge.h" int main(int argc, char* argv[]) { int retval; if (hoge() != 0) { printf("SUCCESS"); retval = 0; } else { printf("FAIL"); retval = 1; } return retval; }
- hoge() の戻り値を書き換えて、main関数の成功/失敗をやりたい
- それぞれの printf の引数が正しいか調べたい
これを、頑張って、実現します。
テストコードの準備
では、一気に書きます。
うろ覚えで書いてるので、エラーになるかも!!
// test_main.c // テストする関数(本物)をリネームする //#define printf _old_printf //#define hoge _old_hoge // 元のコードで include してるヘッダーを先読み込み // #pragma once 等でインクルードガードされてることが前提 #include <stdio.h> #include "hoge.h" // テストする関数(本物)の宣言を消す #undef printf #undef hoge // google test を読み込む #include <gtest/gtest.h> #include <gmock/gmock.h> using ::testing::Return; // テストする関数(モック)の宣言をする #define printf mock_printf #define hoge mock_hoge // モックをガリガリ作る // printf用モック struct _mock_printf { void operator()(const char* format, ...) { apply_internal(format); } MOCK_METHOD1(apply_internal, void(const char*)); } mock_printf; // hoge用モック struct _mock_hoge { int operator()() { return apply_internal(); } MOCK_METHOD0(apply_internal, int()); } mock_hoge; // エントリーポイントをリネーム #define main _old_main // テストしたいコードを取り込み #include "main.c" // エントリーポイントを test_main に変更 #undef main #define test_main main // テスト用コード (google test) TEST(main_function, fail_hoge) { // hoge() は1回だけ呼ばれて 0 を返すよ EXPECT_CALL(mock_hoge, apply_internal()) .Times(1) .WillRepeatedly(Return(0)); // printf は1回だけ呼ばれて FAIL を貰うよ EXPECT_CALL(mock_printf, apply_internal("FAIL")) .Times(1); // 実行結果は 1 だよ EXPECT_EQ(1, _old_main(0, NULL)); } // テスト用コード (google test) TEST(main_function, success_hoge) { // hoge() は1回だけ呼ばれて 1 を返すよ EXPECT_CALL(mock_hoge, apply_internal()) .Times(1) .WillRepeatedly(Return(1)); // printf は1回だけ呼ばれて SUCCESS を貰うよ EXPECT_CALL(mock_printf, apply_internal("SUCCESS")) .Times(1); // 実行結果は 0 だよ EXPECT_EQ(0, _old_main(0, NULL)); } // テストのエントリーポイント int test_main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); ::testing::InitGoogleMock(&argc, argv); return RUN_ALL_TESTS(); }
重要なのは、define/undefで元の関数を使わないようにすること。
インクルードガードが働くと、元のコードを読み込んでも使われないようにできます。
モック関数がグローバル領域にあるので、お行儀はかなり悪いです。
自分の環境では、実行終了後にエラーが発生しました。
予定外 GoogleMockでグローバル変数のモックオブジェクトを作れるか
あと、Google Mockは可変長引数には非対応なので、そこは工夫が必要そうです。
とりあえず、VS2012で動くことは確認したけど、他の環境だとどうだろう?
とりあえず、基地外の未来の自分のために、めもめも。
追記
リネーム用の define が無くても動くっぽい。
むしろ、標準関数をリネームすると、リンクがコケる場合があるので、消しました。