空飛ぶロボットのつくりかた

ロボットをつくるために必要な技術をまとめます。ロボットの未来についても考えたりします。

Google Test C++の使い方(初級)

f:id:robonchu:20181007164810p:plain

やりたいこと

C++のテストがしたい。

Google Testがいい感じという噂。

教科書

  1. 入門ガイド — Google Test ドキュメント日本語訳

  2. 超入門編 — Google Mock ドキュメント日本語訳

  3. クックブック — Google Mock ドキュメント日本語訳

  4. 上級ガイド — Google Test ドキュメント日本語訳

入門まとめ

基本的なアサーション

致命的なアサーション 致命的ではないアサーション 検証内容

ASSERT_TRUE(condition); EXPECT_TRUE(condition); condition が true

ASSERT_FALSE(condition); EXPECT_FALSE(condition); condition が false

ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";

for (int i = 0; i < x.size(); ++i) {
  EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
}

簡単なテスト

プロダクトコード

int Factorial(int n); // nの階乗を返す

テストコード

// 0 の階乗をテスト
TEST(FactorialTest, HandlesZeroInput) {
  EXPECT_EQ(1, Factorial(0));
}

// 正の数の階乗をテスト
TEST(FactorialTest, HandlesPositiveInput) {
  EXPECT_EQ(1, Factorial(1));
  EXPECT_EQ(2, Factorial(2));
  EXPECT_EQ(6, Factorial(3));
  EXPECT_EQ(40320, Factorial(8));
}

テストフィクスチャ:複数のテストで同じデータ設定を使う

プロダクトコード

template <typename E> // E は要素の型
class Queue {
public:
  Queue();
  void Enqueue(const E& element);
  E* Dequeue(); // queue が空の場合は NULL を返します.
  size_t size() const;
...
};

テストのフィクスチャ

class QueueTest : public ::testing::Test {
 protected:
  virtual void SetUp() {
    q1_.Enqueue(1);
    q2_.Enqueue(2);
    q2_.Enqueue(3);
  }

  // virtual void TearDown() {}

  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;
};

このフィクスチャとTEST_F() を利用したテストコード

TEST_F(QueueTest, IsEmptyInitially) {
  EXPECT_EQ(0, q0_.size());
}

TEST_F(QueueTest, DequeueWorks) {
  int* n = q0_.Dequeue();
  EXPECT_EQ(NULL, n);

  n = q1_.Dequeue();
  ASSERT_TRUE(n != NULL);
  EXPECT_EQ(1, *n);
  EXPECT_EQ(0, q1_.size());
  delete n;

  n = q2_.Dequeue();
  ASSERT_TRUE(n != NULL);
  EXPECT_EQ(2, *n);
  EXPECT_EQ(1, q2_.size());
  delete n;
}

テストの呼び出し

テストを定義したら,RUN_ALL_TESTS() でテストを実行できる

#include "this/package/foo.h"
#include "gtest/gtest.h"

namespace {

// テスト対象となるクラス Foo のためのフィクスチャ
class FooTest : public ::testing::Test {
 protected:
  // 以降の関数で中身のないものは自由に削除できます.
  //

  FooTest() {
    // テスト毎に実行される set-up をここに書きます.
  }

  virtual ~FooTest() {
    // テスト毎に実行される,例外を投げない clean-up をここに書きます.
  }

  // コンストラクタとデストラクタでは不十分な場合.
  // 以下のメソッドを定義することができます:

virtual void SetUp() {
  // このコードは,コンストラクタの直後(各テストの直前)
  // に呼び出されます.
}

virtual void TearDown() {
  // このコードは,各テストの直後(デストラクタの直前)
  // に呼び出されます.
}

// ここで宣言されるオブジェクトは,テストケース内の全てのテストで利用できます.
};

// Abc を行う Foo::Bar() メソッドをテストします.
TEST_F(FooTest, MethodBarDoesAbc) {
  const string input_filepath = "this/package/testdata/myinputfile.dat";
  const string output_filepath = "this/package/testdata/myoutputfile.dat";
  Foo f;
  EXPECT_EQ(0, f.Bar(input_filepath, output_filepath));
}

// Xyz を行う Foo をテストします.
TEST_F(FooTest, DoesXyz) {
  // Foo の Xyz を検査
}

}  // namespace

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

Mockまとめ

わからないことがあったとき

基本ステップ

  1. 簡単なマクロを使ってモック化したいインタフェースを記述します.これが,モッククラスの実装に展開されます.

  2. モックオブジェクトを作成し,直感的な構文を利用して Expectation と 動作を規定します.

  3. モックオブジェクトを利用するコードを実行します.Google Mock は,Expectation 違反が起こると即座にそれをキャッチします.

使うための準備

C++ ソースファイル内で, “gtest/gtest.h” と “gmock/gmock.h” を #include するだけで準備は終わり。

モック理解のためのサンプル

class Turtle {
  ...
  virtual ~Turtle() {}
  virtual void PenUp() = 0;
  virtual void PenDown() = 0;
  virtual void Forward(int distance) = 0;
  virtual void Turn(int degrees) = 0;
  virtual void GoTo(int x, int y) = 0;
  virtual int GetX() const = 0;
  virtual int GetY() const = 0;
};

モックの定義

  1. Turtle から MockTurtle クラスを派生させます.

  2. Trutle の仮想関数を調べて,引数の数を数えます.

  3. 派生クラスの public セクションに,MOCK_METHODn();(const メソッドをモック化する場合は, MOCK_CONST_METHODn();)を書きます.ここで n は,引数の数を表しますが,これを数え間違えると,コンパイルエラーで注意されます. 関数のシグネチャを調べて,その関数名をマクロの1番目の引数に,残りを2番目の引数にします.

  4. これを,モック化したい仮想関数全てに対して繰り返します.

MOCK_METHOD0 のように最後の数字は引数の数を指定。

#include "gmock/gmock.h"  // Google Mock はこのヘッダに.
class MockTurtle : public Turtle {
 public:
  ...
  MOCK_METHOD0(PenUp, void());
  MOCK_METHOD0(PenDown, void());
  MOCK_METHOD1(Forward, void(int distance));
  MOCK_METHOD1(Turn, void(int degrees));
  MOCK_METHOD2(GoTo, void(int x, int y));
  MOCK_CONST_METHOD0(GetX, int());
  MOCK_CONST_METHOD0(GetY, int());
};

モックの使い方

  1. モックを制限無く利用できるように,Google Mock の名前をテスト用の名前空間からインポートします

  2. モックオブジェクトを作成します.

  3. その Expectation を設定します

  4. モックを利用したコードを実行します.Google Test のアサーションを利用して結果のチェックを行っても良いでしょう.モックメソッドが期待よりも多く呼び出される,または引数が誤っている,などの場合は,即座にエラーが起こります.

  5. モックがデストラクトされると,その全ての Expectation が満足されたかどうかを Google Mock が自動的に検証します.

#include "path/to/mock-turtle.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using ::testing::AtLeast;                     // #1

TEST(PainterTest, CanDrawSomething) {
  MockTurtle turtle;                          // #2
  EXPECT_CALL(turtle, PenDown())              // #3
      .Times(AtLeast(1));

  Painter painter(&turtle);                   // #4

  EXPECT_TRUE(painter.DrawCircle(0, 0, 10));
}                                             // #5

int main(int argc, char** argv) {
  // 以下の行は,テスト開始前に Google Mock (と Google Test)
  // を初期化するために必ず実行する必要があります.
  ::testing::InitGoogleMock(&argc, argv);
  return RUN_ALL_TESTS();
}

Exceptionの作り方

Expectation を設定するために EXPECT_CALL() マクロを使う。

このマクロには2つの引数があり、1番目はモックオブジェクトで,2番目はメソッドと引数。

using ::testing::Return;...
EXPECT_CALL(turtle, GetX())
    .Times(5)
    .WillOnce(Return(100))
    .WillOnce(Return(150))
    .WillRepeatedly(Return(200));

turtle オブジェクトの GetX() メソッドが5回呼ばれ,最初は 100 を返し,次に 150,それ以降は 200 を返す,という動作のテスト。

所感

unittestに似てる。書きながら使えるようになろう~