0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C言語の関数をgmockでmock化する方法

Last updated at Posted at 2024-08-04

こんにちは。
組み込みエンジニアをしているぽぽんたと申します。
初投稿ですので不慣れな部分もございますが、この記事にたどり着いた方のお役に立てれば嬉しいです。

業務中に、C言語の標準関数をgmockを使ってmock化する方法がわからない、という声をよく聞きます。
難しくはないのですが、意外と方法を知らない人が多いのかな、と思い、記事を書くことにしました。

<2024.08.05 追記>
以下で説明するstat()は、C言語の標準関数ではなく、POSIX標準の関数ですとのご指摘を頂きました。@yaito3014さん、ご指摘ありがとうございます。
標準関数においても、mock化の方法は変わりませんので、ご参考にしていただければ幸いです。

題材:stat()のmock化

この記事では練習として、stat()関数をgmockでmock化してみましょう。

ここではstat()がユーザ定義関数であるIsExist()からコールされているものとします。
IsExist()は引数にファイルのパスを取り、内部でstat()をコールして、ファイルの有無を返します。
最終的なテスト対象はこのIsExist()関数なのですが、この関数をテストするためには、stat()のmock化が必要となります。

IsExist.h
#ifndef ISEXIST_H_
#define ISEXIST_H_

bool IsExist(const char* path);

#endif  // ISEXIST_H_
IsExist.cpp
#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で初期化します。

mock_stat.h
#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_
mock_state.cpp
#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パターンをテストしています。

gtest_IsExist.cpp
#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.

最後までお読みいただいて、ありがとうございました。

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?