こんにちは。
組み込みエンジニアをしているぽぽんたと申します。
初投稿ですので不慣れな部分もございますが、この記事にたどり着いた方のお役に立てれば嬉しいです。
業務中に、C言語の標準関数をgmockを使ってmock化する方法がわからない、という声をよく聞きます。
難しくはないのですが、意外と方法を知らない人が多いのかな、と思い、記事を書くことにしました。
<2024.08.05 追記>
以下で説明するstat()は、C言語の標準関数ではなく、POSIX標準の関数ですとのご指摘を頂きました。@yaito3014さん、ご指摘ありがとうございます。
標準関数においても、mock化の方法は変わりませんので、ご参考にしていただければ幸いです。
題材:stat()のmock化
この記事では練習として、stat()関数をgmockでmock化してみましょう。
ここではstat()がユーザ定義関数であるIsExist()からコールされているものとします。
IsExist()は引数にファイルのパスを取り、内部でstat()をコールして、ファイルの有無を返します。
最終的なテスト対象はこのIsExist()関数なのですが、この関数をテストするためには、stat()のmock化が必要となります。
#ifndef ISEXIST_H_
#define ISEXIST_H_
bool IsExist(const char* path);
#endif // ISEXIST_H_
#include <sys/stat.h>
#include "IsExist.h"
bool IsExist(const char* path) {
struct stat st;
return (stat(path, &st) == 0);
}
stat()のmockを定義する
stat()を差し替えるためのmockクラスとmock関数を定義します。
mock_stat_instanceはmockオブジェクトの実体を格納するためのポインタ変数で、gtestのフィクスチャにて代入するので、ここではnullptrで初期化します。
#ifndef MOCK_STAT_H_
#define MOCK_STAT_H_
#include <sys/stat.h>
#include <gmock/gmock.h>
class MockStat {
public:
MOCK_METHOD2(stat, int(const char* path, struct stat* st));
};
extern MockStat* mock_stat_instance;
#endif // MOCK_STAT_H_
#include "mock_stat.h"
MockStat* mock_stat_instance = nullptr;
extern "C" int stat(const char* path, struct stat* st) {
if (mock_stat_instance == nullptr) {
return -1;
}
return mock_stat_instance->stat(path, st);
}
ここでのポイントは、stat()をmock版として再定義してしまうということです。
この時、stat()にextern "C"
をつけることを忘れないようにしてください。
また、関数のシグネチャ(引数や戻り値の仕様)は、本物のstat()と合わせてください。
「本物のstat()と2重定義になるのでは?」と思う人もいるかもしれませんが、本物のstat()はlibc.soに実装されており、実行時にリンクされます。
同じコンパイル対象に同じシグネチャの関数が複数あるとエラーになりますが、リンク時に重複するだけなら、先に見つかった方が使用されるため、ここで定義したmock版のstat()が使用されます。
テストコードを作成する
ここでは、ファイルが見つかった場合と、見つからなかった場合の2パターンをテストしています。
#include <gtest/gtest.h>
#include "IsExist.h"
#include "mock_stat.h"
using ::testing::_;
using ::testing::Return;
class StatTest : public ::testing::Test {
protected:
void SetUp() override {
mock_stat_instance = &mock_stat_instance_;
}
void TearDown() override {
mock_stat_instance = nullptr;
}
MockStat mock_stat_instance_;
};
TEST_F(StatTest, ExistFile) {
EXPECT_CALL(mock_stat_instance_, stat(_,_))
.WillOnce(Return(0));
EXPECT_EQ(IsExist("dummy.dat"), true);
}
TEST_F(StatTest, NoExistFile) {
EXPECT_CALL(mock_stat_instance_, stat(_,_))
.WillOnce(Return(1));
EXPECT_EQ(IsExist("dummy.dat"), false);
}
テストをビルド
下記のコマンドでビルドします。
g++ gtest_IsExist.cpp mock_stat.cpp IsExist.cpp -lgtest -lgtest_main -lgmock
実行結果
2パターンともPASSしました。
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from StatTest
[ RUN ] StatTest.ExistFile
[ OK ] StatTest.ExistFile (0 ms)
[ RUN ] StatTest.NoExistFile
[ OK ] StatTest.NoExistFile (0 ms)
[----------] 2 tests from StatTest (0 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 2 tests.
最後までお読みいただいて、ありがとうございました。