にわかプラス

にわかが玄人になることを夢見るサイトです。社会や国際のトレンド、プログラミングや電子工作のことについて勉強していきたいです。

Google Mock超入門

sponsor

f:id:hiddenvally333:20200627000531p:plain C++でテストを書くなら、Google Test/Mockを使わない手はない。 製品コードに手を入れることなく、テストが書ける。しかもモックまで簡単にさせてしまう、ほんと神のように便利なツール。

ただ、自分は最初モックの概念や使い方がまったくわからずその便利さを知るのに時間がかかってしまったので、 ここではモックとはなにか?どのように使うのか?といった事がわかるよう記事を書いた。

Mockとは

クラスの動きを模擬するためのオブジェクト。
”モックを挿す” というふうに使われる。

クラスの動きを模擬、とはどういうことか。

たとえばこのようなCarクラスがあり、Wheelクラスを包含しているとする。

f:id:hiddenvally333:20200626233203p:plain
CarクラスがWheelクラスを内包している

このCarクラスはWheelクラスを利用するため、CarクラスをテストするときにはWheelクラスも使わないといけない。

そんなときにWheelクラスの動きを模擬してあげることで、Wheelクラスの実装に関係なく、任意の動きをさせることができる。

任意の動きを模擬できるMock_Wheelクラスを作成し、Wheelクラスと挿し替えることで、Carクラスのテストを行える環境を用意できる。

f:id:hiddenvally333:20200626233200p:plain
WheelクラスをMock_Wheelクラスに挿し替える

Google Mockの使いどころ

基本的にMockはDIパターン(依存性注入)とセットで使われる。

以下の例では両方のPainterはTurtleを包含している点では同じだが、与えられ方が違う。
DIパターンでは、コンストラクタで与えており、そうでない方ははじめからハードコーディングされている。

//InterfaceTurtleは純粋仮想関数のインタフェース、TutleクラスはInterfaceTurtleを継承して実装する
class Turtle : InterfaceTurtle{
    Turtle(){};
    ~Turtle(){};
}

//DIパターン
class Painter{
    //Turtleをコンストラクタで与えられている
    Painter(InterfaceTurtle *turtle){};
    ~Painter(){};
}


//ハードコーディング
class Painter{
    Painter(){};
    ~Painter(){};
    //ハードコーディングで記述
    InterfaceTurtle turtle= new Turtle();
}

テストを行うためにやりたいことはTurtleクラスをGoogle Mockで作ったMock_Turtleに挿し替えること。

DIパターンのほうでは簡単に挿し替えることができる。 コンストラクタで渡すオブジェクトをモックオブジェクトにするだけ。

Mock_turtle turtle;
Painter painter(&turtle);

なぜこのようなことができるのかというと、DIパターンの方のPainterは、Painter自身はTurtleクラスの存在を知らず、インタフェースしか把握していないということ。

つまり、外からインタフェースのどのような具象が与えられるかは把握していない(関係ない)ため、同じインタフェースを持つモック(具象)が与えられても問題なく動くのである。

一方ハードコーディングされている方はどうだろうか。 これはMock_Turtleに挿し替えることは不可能。製品コードに手を入れる必要がある。

//たとえば製品コードを#define DEBUGでデバッグビルド時にMockオブジェクトに挿し替える
class Painter{
    Painter(){};
    ~Painter(){};
    #ifndef DEBUG
    InterfaceTurtle turtle = new Turtle();
    #else
    InterfaceTurtle turtle = new Mock_turtle();
    #endif // !DEBUG
}

テストしやすい構造=DIパターン?

ということは、テストしやすい構造はDIパターンを使っているもので、それ以外はテスト性が悪いと行っていいのだろうか。

基本的にはYES。なので基本的に、他のクラスを使うときにはDIパターンでオブジェクトを渡せないかを考える、といった方針で良いと思う。

ただし、必ずDIパターンにできるかというとそれはまた別問題。

たとえばPainterがTurtleの他に10個のオブジェクトが必要になったとしたとき、コンストラクタの引数がとても多く煩雑になる。

引数の数を変えると呼び出し元の記述もかえなければいけないため、安易に引数の数が変わるような設計はやめておいたほうがいい。

//引数が多くて煩雑
Painter painter(&turtle,&SeaTurtle,&Crane,&Elephant,&Eagle,・・・);

その他にも、無理にDIパターンを使うことで設計が崩れてしまう、といった場合にはDIパターン適用によるテスト性の向上と天秤にかけて考える必要がある。

おわりに

簡単にクラスの動きを模擬できるGoogle Mock。どのようなものかわかっていただけただろうか。 DIパターンで作られていれば、簡単にクラスの動きを模擬できる神のような存在と、感じていただけただろうと思う。

Google Test/Mockなどのテストフレームワークを使ってテストを書いてみると、「テストのしやすさ」といった設計の観点を体感できるためぜひ一度は試してみるのはいかがでしょう。