Google Test勉強録 (1) CMakeでのビルド

# 1. Google Testとは
[Google Test](https://github.com/google/googletest) はC/C++用のテストフレームワークです。

現在、githubの公式リポジトリでは、

- Google Test (gtest): 単体テストフレームワーク
- Google Mock (gmock): モッキングフレームワーク

の2つが同梱される形で配布されていますが、今回はこれらのうちgtestの使い方を調べます。

# 2. 目的

本記事は、簡単な単体テストが書けるようになるまでの個人的な勉強録です。

- 本記事では主に、Google Testを含むプロジェクトするビルドする方法や、CMakeとの連携方法に興味があります。
- Google Test関係の文法の説明は行いません。資料としては [公式チュートリアル](https://github.com/google/googletest/blob/master/googletest/docs/Primer.md) や [その日本語訳](http://opencv.jp/googletestdocs/index.html) があります。 

# 3. 環境構築 (Google Testの準備)

以下の内容はmacOS Sierra (10.12.6) で試しています。CMakeのバージョンは3.11.0-rc1を使っていますが、より古いバージョンでも動く可能性はあります。

## 3.1. ビルドツール

[CMake](https://cmake.org/) を使います。

大枠としては、下のようなプロジェクト構成にしたいです。

```
.
├── CMakeLists.txt
├── src // ここにソースコードを置きたい
└── test // srcのコードのテストを置きたい
```

それにあたり、次の2点が気になるので調べます。

1. Google Testはどこに置けばよいか
1. CMakeとGoogle Testをどう連携すればよいか (i.e. `CMakeLists.txt`をどう書くか)

2は1に依存します。つまり、Google Test自体をどこに置いたかで、CMakeがGoogle Testの依存関係をどうやって解決すればよいかが変わります。

## 3.2. Google Testをどこに置くか

当然テストもC++で書くので、CMakeがGoogle Testを無事見つけられる必要がありますが、どこに置くかが問題です。[kaizoumanさんのブログ](http://www.kaizou.org/2014/11/gtest-cmake/)によると、少なくとも3通りの方法があります：

1. ビルド済みのGoogle Testをプロジェクト外部に置く
2. Google Testごとプロジェクトに含めてしまう
3. CMakeのExternalProjectとして扱う

### 3.2.1. ビルド済みのGoogle Testをプロジェクト外部に置く

ビルド済みのGoogle Testをあらかじめローカルの適当な場所に置いておく方法です。個人開発ではこれが最も楽だと思われます。

(1) ダウンロード & ビルド

```
$ git clone https://github.com/google/googletest.git
$ cd googletest
$ mkdir build // 適当な名前
$ cd build
$ cmake ..
$ make
```
gtestの静的ライブラリは `build/googlemock/gtest` に生成されていました。歴史的な経緯で、googlemockがgoogletestを含むようになり、その後googletestがgooglemockを含むようになったという事情があるらしいです…。

(2) 生成物を適当なところに移動

ヘッダファイルと静的ライブラリをどこか見つけやすいところに


例えば、今回はヘッダファイルを `/usr/local/include/gtest`、静的ライブラリを `/usr/local/lib` に置きます (gmockもついでにコピーしています)。

```
$ cp -r googlemock/include/gmock /usr/local/include/gmock
$ cp -r googletest/include/gtest /usr/local/include/gtest
$ cp build/googlemock/*.a /usr/local/lib/
$ cp build/googlemock/gtest/*.a /usr/local/lib/
```

ついでに、`pkg-config` から見つけられる場所に設定ファイルを置いておくことにしました。

```
$ cp build/*.pc /usr/local/lib/pkgconfig/
```
ちゃんと探せているか確認してみます。

```
$ pkg-config --modversion gtest

1.9.0

$ pkg-config --cflags gtest

-DGTEST_HAS_PTHREAD=1 -I/usr/local/include

$ pkg-config --libs gtest

-L/usr/local/lib -lgtest
```
良さそうです。なお、`*.pc`の置き場所を`PKG_CONFIG_PATH` という環境変数に追加しておくのでも良いようです (参考: [pkg-configに対応する](http://ultraist.hatenablog.com/entry/20070624/pkgconfig))。

(3) CMakeListの書き方

ここでは、CMakeからgtestを「探す」方法のみ書きます (使う方法は後述)。

(3.1) pkg-configを明示的に使う

CMakeには [pkg-config用のモジュール](https://cmake.org/cmake/help/latest/module/FindPkgConfig.html) が用意されているため、これを利用してgtestの場所を探すことができます。`CMakeLists.txt`に次のような設定を書きます。

CMakeLists.txt

```
...
find_package(PkgConfig)
pkg_search_module(GTEST REQUIRED gtest_main)
...
```
うまくいけば `GTEST_CFLAGS ` や `GTEST_LDFLAGS` などの変数群が使えるようになります。

参考：

- [Using GoogleTest from various build systems](https://github.com/google/googletest/blob/master/googletest/docs/Pkgconfig.md)
- [CMake で pkg-config を使う](https://qiita.com/janus_wel/items/e70695670c22a0331451)

(3.2) CMakeのFindモジュールを使う

CMakeの比較的新しいバージョンであれば、Google Testを [探すモジュール](https://cmake.org/cmake/help/latest/module/FindGTest.html) が用意されています。

CMakeLists.txt

```
...
find_package(GTest REQUIRED)
...
```
よくわかってないですが、内部的にはpkg-configを利用しているらしい (参考： [CMake:How To Find Libraries](https://cmake.org/Wiki/CMake:How_To_Find_Libraries)) のですが、おそらくセットされる変数などがやや異なります。


### 3.2.2. Google Testごとプロジェクトに含めてしまう

テストも含めた状態でコードを公開したい場合、上の方法だと難しいかもしれません。そこで、Google Testをいっそ丸ごとプロジェクトに含めてしまう方法があります。例えば、こういう状態になります。

```
.
├── CMakeLists.txt
├── src
└── test
    └── googletest
```

Google Test自体はBSD3条項ライセンスなので、そこに注意すればこのままGithub等にも置けます。見た目は美しくないかもしれません。

### 3.2.3. CMakeのExternalProjectとして扱う

CMakeには [ExternalProject](https://cmake.org/cmake/help/latest/module/ExternalProject.html) という機能があり、外部プロジェクトのコードを自動でダウンロードしてきてビルドするということができます。

結論からいうと、これをいい感じに隠蔽して使いやすくしたCMakeのモジュールが公開されているので、それを使います。

[Crascit/DownloadProject -- Github](https://github.com/Crascit/DownloadProject)

使い方はリンク先のexampleがわかりやすいです。MITライセンスです。

例えばこういう感じで置くとします。

```
.
├── CMakeLists.txt
├── src
└── test
└── cmake
    └── DownloadProject
```

そして、`CMakeLists.txt` にこういう感じのことを書くと、cmake実行時に自動でダウンロードを行います。最終的には2の方法と似た状況になるので、

CMakeLists.txt

```
cmake_minimum_required(VERSION 3.2)

...

include(cmake/DownloadProject/DownloadProject.cmake)
download_project(
    PROJ googletest
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG master
    UPDATE_DISCONNECTED 1
)

add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR})

...
```

参考：

- [Building GoogleTest and GoogleMock directly in a CMake project](https://crascit.com/2015/07/25/cmake-gtest/)
- [Unit testing with GoogleTest and CMake](http://www.kaizou.org/2014/11/gtest-cmake/)
- [CMake ExternalProject 事始め](https://qiita.com/trairia/items/d20860d61f0e1eb2fb72)

# 4. 簡単なテストの例

上記の3.1.1.と3.1.3.の方法を使って、簡単なテストをビルドします。
ここでは、ビルドできるかだけに興味があるため、[公式のサンプル](https://github.com/google/googletest/blob/master/googletest/samples/sample1_unittest.cc)にならって、階乗を計算するだけのコードを書きます。

*/src/sample1.h

```cpp
#ifndef SAMPLE1_H_
#define SAMPLE1_H_

int factorial(int n);

#endif
```

*/src/sample1.cpp

```cpp
#include <iostream>
#include "sample1.h"

// 注: nが負のときは1になる
int factorial(int n) {
  int result = 1;
  for (int i = 1; i <= n; ++i) {
    result *= i;
  }
  return result;
}

```

`TEST(test_case_name, test_name)` マクロを使ってテストを書きます。この場合
 `test_case_name` と `test_name` には任意の文字列を設定できます。下の例では、factorial関数の値を `EXPECT_EQ` でテストしていますが、失敗するものを故意にひとつだけ混ぜてあります。

*/test/test_sample1.cpp

```cpp
#include "sample1.h"
#include "gtest/gtest.h"

TEST(FactorialTest, Negative) {
  EXPECT_EQ(1, factorial(-5));
  EXPECT_EQ(1, factorial(-10));
}

TEST(FactorialTest, Zero) {
  EXPECT_EQ(1, factorial(0));
}

TEST(FactorialTest, Positive) {
  EXPECT_EQ(1, factorial(1));
  EXPECT_EQ(2, factorial(2));
  EXPECT_EQ(120, factorial(5));
}

// 失敗するべきテスト
TEST(FactorialTest, Failure) {
  EXPECT_EQ(42, factorial(0));
}
```

## 4.1. ローカルのGoogle Testを使う場合

3.2.1.のように、ローカルにあるgtestを使う場合を考えます。このような構成にしてみます。

```
.
├── CMakeLists.txt
├── src
│   ├── CMakeLists.txt
│   ├── sample1.cpp
│   └── sample1.h
└── test
    ├── CMakeLists.txt
    └── test_sample1.cpp
```

CMakelists.txt

```
cmake_minimum_required(VERSION 3.10)
project(Sample1)
enable_testing()
add_subdirectory(src)
add_subdirectory(test)
```

src/CMakelists.txt (静的ライブラリを作っています)

```
cmake_minimum_required(VERSION 3.10)
add_library(Sample1 STATIC sample1.cpp)
```

test/CMakelists.txt

```
cmake_minimum_required(VERSION 3.10)

find_package(GTest REQUIRED)
include(GoogleTest)

add_executable(TestSample1 test_sample1.cpp)
target_link_libraries(TestSample1 Sample1 GTest::GTest GTest::Main)
include_directories(${PROJECT_SOURCE_DIR}/src ${GTEST_INCLUDE_DIRS})

# Google Testの各テストケースごとにCTestのテストを作成する
gtest_add_tests(TARGET TestSample1)

# CTestのテストをひとつだけ作成する
#add_test(NAME AllTests COMMAND TestSample1)
```

動かしてみます。

```
$ mkdir build
$ cd build
$ cmake ..
$ make
$ make test
```

結果

```
Running tests...
Test project ${project_dir}/build
    Start 1: FactorialTest.Negative
1/4 Test #1: FactorialTest.Negative ...........   Passed    0.02 sec
    Start 2: FactorialTest.Zero
2/4 Test #2: FactorialTest.Zero ...............   Passed    0.01 sec
    Start 3: FactorialTest.Positive
3/4 Test #3: FactorialTest.Positive ...........   Passed    0.01 sec
    Start 4: FactorialTest.Failure
4/4 Test #4: FactorialTest.Failure ............***Failed    0.01 sec

75% tests passed, 1 tests failed out of 4

Total Test time (real) =   0.05 sec

The following tests FAILED:
	  4 - FactorialTest.Failure (Failed)
Errors while running CTest
make: *** [test] Error 8
```

Test #4: FactorialTest.Failureが無事 (?) 失敗しています。

テストの追加には、CMakeの [GoogleTestモジュール](https://cmake.org/cmake/help/latest/module/GoogleTest.html#module:GoogleTest) を使っています。特に、`gtest_add_tests` はCMake v3.10から追加された機能で、Google Testの各テストケースごとにCTestのテストを個別に作成します (この場合は4つ作成されます)。

ちなみに、この部分を

```
add_test(NAME AllTests COMMAND TestSample1)
```

と変更すると、次のような結果になりました。

```
Running tests...
Test project ${project_dir}/build
    Start 1: AllTests
1/1 Test #1: AllTests .........................***Failed    0.02 sec

0% tests passed, 1 tests failed out of 1

Total Test time (real) =   0.03 sec

The following tests FAILED:
	  1 - AllTests (Failed)
Errors while running CTest
make: *** [test] Error 8
```

確かに失敗しているっぽいのですが、どのテストケースが失敗したのかはわかりません。

## 4.2 Google Testをダウンロードしてきて使う場合

次に、3.2.3.のように、Google TestをダウンロードするところまでCMakeにやってもらう場合を考えます。

まず、先ほどと同じプロジェクト内のどこかに `DownloadProject.cmake` を含めます。

```
.
├── CMakeLists.txt
├── cmake
│   └── DownloadProject
│       └── (ここに https://github.com/Crascit/DownloadProject をクローンしてくる)
├── src
│   ├── CMakeLists.txt
│   ├── sample1.cpp
│   └── sample1.h
└── test
    ├── CMakeLists.txt
    └── test_sample1.cpp
```

`test/CMakeLists.txt` のみ次のように変更します。

```
cmake_minimum_required(VERSION 3.10)

include(${PROJECT_SOURCE_DIR}/cmake/DownloadProject/DownloadProject.cmake)
download_project(PROJ googletest
                GIT_REPOSITORY https://github.com/google/googletest.git
                GIT_TAG master
                UPDATE_DISCONNECTED 1
)

add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR})

add_executable(TestSample1 test_sample1.cpp)
target_link_libraries(TestSample1 Sample1 gtest_main)
include_directories(${PROJECT_SOURCE_DIR}/src)

include(GoogleTest)
gtest_add_tests(TARGET TestSample1)
```

動かしてみます（上の例と同じ）。

```
$ mkdir build
$ cd build
$ cmake ..
$ make
$ make test
```

`cmake` コマンドを打つと何やらダウンロードが始まって、`build/googletest-*` に色々と展開されます。`make` のところでGoogle Testが実際にビルドされるので多少時間がかかります。

結果：

```
Running tests...
Test project ${project_dir}/build
    Start 1: FactorialTest.Negative
1/4 Test #1: FactorialTest.Negative ...........   Passed    0.02 sec
    Start 2: FactorialTest.Zero
2/4 Test #2: FactorialTest.Zero ...............   Passed    0.01 sec
    Start 3: FactorialTest.Positive
3/4 Test #3: FactorialTest.Positive ...........   Passed    0.01 sec
    Start 4: FactorialTest.Failure
4/4 Test #4: FactorialTest.Failure ............***Failed    0.01 sec

75% tests passed, 1 tests failed out of 4

Total Test time (real) =   0.05 sec

The following tests FAILED:
	  4 - FactorialTest.Failure (Failed)
Errors while running CTest
make: *** [test] Error 8
```

正しく動いているように見えます。

# 5. まとめ


- gtestを使ったテストをCMakeでビルドする方法を勉強した
- 個人的な利用ではローカルにあるものを使えば良さそう
- gtestを含むコードを公開するときはDownloadProjectが便利そう

今回の例は下に置きました。
https://github.com/ktrmnm/gtest-learn