C言語でGoogleMockをLink-Time Substitutionしてみた

    前回の続き。

    前回までが、関数ポインタを使った置換でGoogleMockを使っていたが、 別の方法で。

    Makefileを書き換えて、リンク時に置換してしまう。(リンク時置換 link-time Substitution)

    C言語でテストをするときに、関数をダミー関数と置き換えたいことがある。

    関数がテストしたいファイルと同じ場所にある場合は、

    関数ポインタをつかって置換することができる。

    しかし、関数がテストしたいファイルと別の場所にあるときは、

    ファイルごと置換してしまうことができる。

    ダミー関数はもともと置き換えたい関数と同関数名として、

    テストしたいファイルとは別のファイルに定義しておく。

    コンパイルをかけるときに、Makefileを置き換えたい関数が定義してあるファイルをコンパイル対象から外してしまい、その代わりに、ダミー関数が定義してあるファイルを追加する。

    こうすることによって、プロダクトコードに手を加えずに、

    コンパイル時に関数をダミー関数を置き換えることができる。

    これを、Link-time-Substitutionという。

    というわけで、このテクニックを使って、

    ブロダクトコードにモックを強引にねじ込んでテストした。

    sample.c(プロダクトコード)

    [c]

    include “sample.h”

    int get_ret(void)

    {

    int num = 0;

    int ret;

    NUM dummy;

    RET dummy2;

    dummy.num = 2;

    dummy.count = 3;

    /\N 数をライブラリより取得 */

    num = get_num(&dummy, &dummy2);

    /\N 結果によって分岐 */

    if( num == 1 ) {

    ret = 10;

    }

    else if( num == 2 ){

    ret = 20;

    }

    else {

    ret = 30;

    }

    return ret;

    }

    [/c]

    sample.h

    [c]

    ifndef SAMPLE_H_

    define SAMPLE_H_

    include “numlib.h”

    int get_ret(void);

    endif /* SAMPLE_H_ */

    [/c]

    numlib.c(置き換えたい関数)

    [c]

    int get_num()

    {

    return rand();

    }

    [/c]

    numlib.h(置き換えたい関数のヘッダファイル)

    [c]

    typedef struct NUM

    {

    int num;

    int count;

    }NUM;

    typedef struct RET

    {

    int ret;

    int count;

    }RET;

    int get_num(NUM *num, RET *ret);

    [/c]

    gmock_test.cpp(テストコード)

    [c]

    extern “C”

    {

    include “sample.h”

    int get_num(NUM *num, RET *ret);

    }

    include “gtest/gtest.h” /\N GoogleTest インクルード */

    include “gmock/gmock.h” /\N GoogleMock インクルード */

    using ::testing::Return;

    using ::testing::_;

    using ::testing::Eq;

    /\N モッククラス */

    class MockNum

    {

    public:

    MOCK_METHOD2(get_num2, int(int num, int count) );

    } mock;

    /\N テスト */

    class MockNumTest : public ::testing::Test

    {

    public:

    virtual void SetUp()

    {

    }

    virtual void TearDown()

    {

    }

    };

    int get_num(NUM *num, RET *ret)

    {

    return mock.get_num2(num->num, num->count);

    }

    TEST_F(MockNumTest, return1)

    {

    EXPECT_CALL(mock, get_num2(_,_) ).WillOnce(Return(1));

    EXPECT_EQ(10, get_ret() );

    }

    TEST_F(MockNumTest, return2)

    {

    EXPECT_CALL(mock, get_num2(2,3) ).WillOnce(Return(2));

    EXPECT_EQ(20, get_ret() );

    }

    TEST_F(MockNumTest, return3)

    {

    EXPECT_CALL(mock, get_num2(Eq(2),Eq(3)) ).WillOnce(Return(3));

    EXPECT_EQ(30, get_ret() );

    }

    [/c]

     

    C言語からC++での関数が使えるように、C++のコードではextern “C”をつかう。

    extern “C”

    {

    include “sample.h”

    int get_num(NUM *num, RET *ret);

    }

    たいていの場合、Mockで置き換えたい関数は、他人のコードなので、

    ファイルが独立していることが多い。

    関数ポインタを使う方法よりも、こっちのほうがよくつかったりする。