LoginSignup
21
16

More than 5 years have passed since last update.

Google Mockを使ってみた

Posted at

Google TestとかGoogle Mockとか言うものがあることを知ったので、少し試してみた。

ドキュメントの日本語訳が、opencv.jpにあるので、そこを見ながら適当に。

簡単に言うと、Google TestがC++のテストフレームワークで、Google Mock はモックオブジェクトを簡単に記述できるフレームワーク。

良いテスト対象がなかったので、自分で作っている select(2) wrapper を対象にする。
テストを書いてみた結果は、同リポジトリのgtestブランチにある。

Google Mock のビルド

Google Mockのプロジェクトページを見ると、ページ左にDownloadsと言うところがあるので、そこからzipファイルをダウンロードする。
この記事を書いているときの最新バージョンは、1.7.0らしい。

% unzip gmock-1.7.0.zip
% cd gmock-1.7.0
% ./configure
% make

ちなみに、gmockはHomebrewのblacklist.rbに載っていて、

Installing gmock system-wide is not recommended; it should be vendored
in your projects that use it.

と書いてある。
gmockのMakefileでも、make installしようとすると

'make install' is dangerous and not supported. Instead, see README for how to integrate Google Test into your build system.

と言われる。
とりあえずビルドに成功すると、以下のような状態になる。

gmock-1.7.0/include
gmockのヘッダファイル
gmock-1.7.0/lib
gmockのライブラリ(libgmock.la)
gmock-1.7.0/gtest/include
gtestのヘッダファイル
gmock-1.7.0/gtest/lib
gtestのライブラリ(libgtest.la)

Mac OS Xで試したのだけれど、libgmock.la は標準のldでは読めないようで、そのままではリンクできなかった。(gmock-1.7.0/lib/.libs/libgmock.dylibならばリンクできるが、実行時に探す場所がconfigure の --prefixで指定した場所になるため、make installができない状態では使いにくい)

テスト対象とモック対象を決める

今回の対象はfselectと言うクラスで、システムコールselect, popen, read, write等を使っている。
fselectを使うコードはないので、fselectをモック化しても意味がない。
そこで、今回はfselect自身をテスト対象とし、select(2)をモック化することにする。

モック化のためのソース修正

gmockは、インターフェイスと実装クラスがあるようなケースで、インターフェイスをモック化するものなので、システムコールであるselect(2)をそのままモック化することはできない。
gmockのクックブックには、フリー関数をモック化すると言うセクションがあり、そこによると関数をインターフェイス化し、具象クラスを派生化させ、インターフェイスをモック化しろとある。
今回の場合わざわざインターフェイスと具象クラスに分けなくても良いだろうと思ったので、Selectと言うクラスをデフォルト実装付きのインターフェイスとしてくくりだした(select.h/select.cc)
後は、もともとあるfselectと言うクラスが、システムコールselect(2)を呼ぶ代わりに新しいSelect(かそのサブクラス)を呼んでくれれば良い。

クックブックの「非仮想メソッドをモック化する」には、templateを使えば良いんじゃないの?的なことが書いてあるのだが、fselectをtemplateクラスにしてしまうと、実装をほとんどヘッダに書かないといけなくなるため、苦肉の策としてコンストラクタでSelect*を受け取ることにした。省略すると、今回分離したSelectを内部で生成する。

モック生成

後は、モッククラスを作る。(mock_select.h)
短いのでソースを載せてしまうと、

mock_select.h
#ifndef __MOCK_SELECT_H
#define __MOCK_SELECT_H

#include "select.h"
#include "gmock/gmock.h"

namespace wl {
    class MockSelect: public Select {
    public:
    MOCK_METHOD5(select, int(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout));
    };
}

#endif // __MOCK_SELECT_H

テストコード

テストコードは、gtest.cc
わかりやすいところで、selectの返値を偽造するケース

gtest.cc
// 0(stdin)を監視し、selectがエラーを返すケース
TEST(FSelectTest, select_02) {
    wl::MockSelect *mock = new wl::MockSelect;
    EXPECT_CALL(*mock, select(4, testing::_, testing::_, testing::_, 0))
        .Times(AtLeast(1))
        .WillOnce(Return(-1));

    wl::fselect fselect(mock);
    bool stop;
    fselect.read_watch(0);
    int result = fselect.select(stop);
    ASSERT_EQ(-1, result);
}

EXPECT_CALLで、selectが1回は呼ばれること、呼ばれたときには-1を返すことを記述している。
そして、実際にfselect.select()を呼んだ後で、ASSERT_EQで返値のチェックをしている。

続いて、モックで引数を更新する例。
クックブックには、「副作用をモック化する」と言うセクションがあり、SetArgPointeeが紹介されているが、selectの引数はfd_setのポインタであり、ポインタが指す構造体のメンバの更新には使えそうにない(たぶん・・・)
そこで、同じくクックブックの「関数/メソッド/ファンクタを Action として利用する」のところを読むと、Invokeを使うと好きなことができるらしい。

gtest.cc
// 0(stdin)を監視し、0が読み込み可能になるケース
TEST(FSelectTest, select_01) {
    wl::MockSelect *mock = new wl::MockSelect;
    EXPECT_CALL(*mock, select(4, testing::_, testing::_, testing::_, 0))
        .Times(AtLeast(1))
        .WillOnce(Invoke([](int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout) {
                    FD_ZERO(readfds);
                    FD_ZERO(writefds);
                    FD_ZERO(errorfds);
                    FD_SET(0, readfds);
                    return 1;
                }));

    wl::fselect fselect(mock);
    bool stop;
    fselect.read_watch(0);
    int result = fselect.select(stop);
    ASSERT_EQ(1, result);
    ASSERT_EQ(true, fselect.read_isready(0));
}

そこで、こんな感じにlambdaを使ってあげると、引数の変更でもなんでもできるようになる。

テストのビルド

% make
c++ -I../gmock-1.7.0/include -I../gmock-1.7.0/gtest/include -g --std=c++11 -pthread -Wall    -c -o gtest.o gtest.cc
c++ -I../gmock-1.7.0/include -I../gmock-1.7.0/gtest/include -g --std=c++11 -pthread -Wall    -c -o fselect.o fselect.cc
c++ -I../gmock-1.7.0/include -I../gmock-1.7.0/gtest/include -g --std=c++11 -pthread -Wall    -c -o select.o select.cc
c++ -o gtest gtest.o fselect.o select.o ../gmock-1.7.0/src/gmock-all.o ../gmock-1.7.0/gtest/src/gtest-all.o

READMEの通り、gmockとgtestのincludeにインクルードパスを通して、gmock-all.oとgtest-all.oをリンクしてやれば良い。

テストの実行

% ./gtest
[==========] Running 3 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 3 tests from FSelectTest
[ RUN      ] FSelectTest.select_01
[       OK ] FSelectTest.select_01 (1 ms)
[ RUN      ] FSelectTest.select_02
[       OK ] FSelectTest.select_02 (0 ms)
[ RUN      ] FSelectTest.select_03
[       OK ] FSelectTest.select_03 (0 ms)
[----------] 3 tests from FSelectTest (1 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test case ran. (1 ms total)
[  PASSED  ] 3 tests.

gmockだけでなくgtest自体も今回初めて触ったので、まだ良くわからないけど、./gtest --help とかやるといろいろオプションが出てきたり、xml出力もできたりするようなので、もうちょっと調べてみようと思う。

gmockについては、かなり綺麗にモジュールが切り分けられていないと、テストは難しいと感じた。まあ、それはgmockに限った話ではなく、自動テスト一般に言えることなので、普段からテストを意識したコードを書けるかどうかが効いてくるんだろう。(恥ずかしながら私は全然駄目だ)

21
16
0

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
21
16