SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

Google Mock:はじめの一歩

Google製Unit Test FrameworkにMockが登場

  • X ポスト
  • このエントリーをはてなブックマークに追加

 CodeZineでgtest(Google Test)を紹介したのは4年も前のこと。ひさしぶりにgtestのGitHubを覗いてみたらgtest 1.8.0がリリースされていました。この版の以前との大きな違いは"Mockのサポート"のようです。C++でMockを提供してくれるUnit Test Frameworkはそんなに多くはなかったと記憶しています。 Google製Mockの使い心地を試してみることにしました。

  • X ポスト
  • このエントリーをはてなブックマークに追加

Mockとは

 Unit Testは通常低いレイヤの関数/クラスから行われます。関数f()をテストするとき、f()がg()を呼んでいるなら、まずg()をテストして正しく動いてくれることを確認してからでないとf()を十分にテストできません。

 ところがいろんな事情でg()が正しく動作することを検証できぬままf()をテストせざるを得ないことがあります。例えばスケジュールの都合でg()の完成がかなり遅れることになった、とか。g()はデータベースにアクセスするんだけど、このg()をテストに用いると大事なデータを破壊しかねない、とか。あるいはg()は完成し正しく動作することは確認済みだけど、"わざと"エラーを起こすことが極めて困難(ハードウェアのトラブルに起因するエラーとか)なために、これを呼んでいるf()の異常系のテストができない、とか。

 こんなときに使われるのがMockです。Mockはこの例でいえばf()から呼ばれる関数g()のニセモノ/ハリボテで、あたかも本物のg()であるかのように動作させることができます。さらにMock関数g()はその挙動を外部からコントロールできます。例えばf()の内部からg()が呼ばれる前に"次の呼び出し時にはわざとエラーを返せ"だとか"最初の2回はtrue、それ以降はfalseを返せ"のように、あらかじめMock-g()にダンドリを仕込んでおいてそのダンドリ通りに反応させることでf()の動作が正しいかをテストできます。Mockはいわば"プログラマブルなハリボテ"とでも申しましょうか。

Mockの作成

 gmock(Google Mock)の味見に取り掛かりましょう。GitHub上のgoogle testにあるstable-releaseからrelease-1.8.0のソースコード(zip)をダウンロードし、適当なディレクトリに展開します(このディレクトリを以降<gtest_root>と表記します)。

fig01
fig01

 コマンドライン(Visual Studio コマンドプロンプト)から<gtest_root>に移動し、以下のbatchを実行してライブラリを作ります:

list01 buildlib.bat
:/ <GTEST_ROOT>を適宜書き換えて
set GROOT=<GTEST_ROOT>
set GT=%GROOT%\googletest
set GM=%GROOT%\googlemock
:/ ↓ARCHは X86 or X64
set ARCH=X64

cl -c -MD -EHsc -I%GT%\include                -I%GT% %GT%\src\gtest-all.cc %GT%\src\gtest_main.cc
cl -c -MD -EHsc -I%GM%\include -I%GT%\include -I%GM% %GM%\src\gmock-all.cc %GM%\src\gmock_main.cc

mkdir lib
lib /OUT:lib\gtest.lib      /MACHINE:%ARCH% gtest-all.obj
lib /OUT:lib\gtest_main.lib /MACHINE:%ARCH% gtest_main.obj

lib /OUT:lib\gmock.lib      /MACHINE:%ARCH% gmock-all.obj
lib /OUT:lib\gmock_main.lib /MACHINE:%ARCH% gmock_main.obj

 gtest/gmockの準備はこれで完了。テストのコンパイル/リンク時はinclude-pathは<gtest_root>\googletest\includeと<gtest_root>\googlemock\include、library-pathは<gtest_root>\libを設定し、ライブラリgtest.libとgmock.libをリンクします。ついでにgmock_main.libをリンクするとmain()の実装が要らなくなります。

 さてと、ここからgmockの使い方。テスト対象のサンプルとして、とある口座:Accountを対象に複数の取引(預入/引出)を一括して行う関数:dealを用意しました。

list02 deal.h
#ifndef DEAL_H_
#define DEAL_H_

#include "Account.h"
#include <queue>

int deal(Account* acc, std::queue<int>& amounts);

#endif

 Accountのメンバ関数はbool deposit(int amount)とbool withdraw(int amount)、それぞれ引数:amountに示された額の預入と引出を行い、成功/失敗に応じてtrue/falseを返します。ただし、amountは0より大きくなくてはならないものとします。

 一連の取引を行う関数:dealは引数:amountsから取引額を一つずつ取り出し、それが正なら預入/負なら引出をaccに対して行い、取引が成功すればamountsから取り除きます(取引額が0ならなにもせず取引成功扱いとします)。途中一度でも取引に失敗したら、それ以降の取引を行いません。dealは成功した取引の回数を返します。従って例えば5つの取引がdealに渡されたとき、全取引が成功すればamountsは空になりdealは5を返しますし、最初の取引が失敗すればamountsはそのままで0が返ることになります。

 ……と、以上の仕様に従うなら、各取引の成功/失敗にかかわらず、「deal前のqueueの長さ=dealの戻り値+deal後のqueueの長さ」となるはず。gtestによるdealのテストは、例えばこんなコードになります:

list03
TEST(case01,test00) {
  Account acc;
  std::queue<int> amounts;
  // amountに取引額を設定 正:預入/負:引出
  amounts.push(2); amounts.push(-1); amounts.push(3);

  int expected = amounts.size();
  int result = deal(&acc, amounts);
  EXPECT_EQ(expected, result + amounts.size()); // 結果の検証
}

 ところでclass Accountは仕様は決まってはいるものの、まだ実装されていません。たとえ完成していても現実の銀行口座を操作するマジもんのAccountをテストに使うほどの度胸はありませんし、たとえ使えたとしても取引を"わざと"成功/失敗させるのは困難でしょうね。

 gmockを使ってAccountのハリボテ:MockAccountを作り、Accountの代役を務めさせることにします。

list04 Acount.h
#ifndef ACCOUNT_H_
#define ACCOUNT_H_

class Account {
public:
  virtual bool deposit(int amount) =0;
  virtual bool withdraw(int amount) =0;
};
#endif

 Accountの実装は要らないのでメンバ関数はすべてpure-virtualです。

 このAccountからMockAccountを導出します。

list05 MockAcount.h
#ifndef MOCKACCOUNT_H_
#define MOCKACCOUNT_H_

#include "Account.h"
#include <gmock/gmock.h>

class MockAccount : public Account {
public:
  MOCK_METHOD1(deposit,  bool(int));
  MOCK_METHOD1(withdraw, bool(int));
};

#endif

 マクロ:MOCK_METHOD#(関数名, 戻り型(引数型...); で、基底クラスで宣言されたメンバ関数をoverrideします。マクロ名末尾の数字は引数の数、constメンバ関数にはMOCK_CONST_METHOD#を用います。Mock作りはこれでオシマイ、簡単でしょ? Acountの代役としてMockAccountの各メンバ関数が呼び出されたときに、テストケースに応じてに異なる挙動を与えることができます。

 おためしにテキトーに実装しちゃった(きっとバグだらけの)テスト対象:dealを書いておきました。こいつをgtestとgmockでテストします。

list06 deal.cpp
#include "deal.h"

// きっとバグだらけの deal
int deal(Account* acc, std::queue<int>& amounts) {
  int count = 0; // 成功した取引数
  // 取引がなくなるまで
  while ( !amounts.empty() ) {
    // 取引を一つ取り出し
    int amount = amounts.front();
    amounts.pop();
    // 取引額の正負に応じて預入/払出
    bool success = (amount > 0) ? acc->deposit(amount) : acc->withdraw(amount);
    if ( success ) { // 成功したらcount-up
      ++count;
    } else {
      break; // 失敗したらそこで中断
    }
  }
  return count; // 成功数を返す
}

会員登録無料すると、続きをお読みいただけます

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

次のページ
Mockを使ったテスト

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

επιστημη(エピステーメー)

C++に首まで浸かったプログラマ。Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"りわんくま同盟でたまにセッションスピーカやったり中国茶淹れてにわか茶...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/9560 2016/10/26 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング