【2022年版】CMakeプロジェクトでgoogletest

# はじめに

CMakeで構成されたC++のプロジェクトに、[googletest](https://github.com/google/googletest/)によるユニットテストを組み込む方法です。

2020年末の自身の記事「[CMakeプロジェクトでgoogletest](https://qiita.com/utkamioka/items/c931e9726d06cdf4edb7)」のリンクを手繰ってみたら公式の手順（["Incorporating Into An Existing CMake Project"](https://github.com/google/googletest/tree/main/googletest#incorporating-into-an-existing-cmake-project)）が結構変わっていたので、それへの対応版です。

## この方法のメリット

1. OSが持つgoogletestライブラリに依存しないので、
    * （yumやaptのための）管理者権限が不要です。
    * 誰が、どこで（どのOSで）実行しても、安定した結果が得られます。
2. ビルドの実行ごとにgoogletestのソースコードをcloneするので、
    * 最新のgoogletestを利用できます。
    * ソースリポジトリを汚さず、必要最小限のものだけでプロジェクトを構成できます。

# 手順

## 手順0: 普通のCMakeプロジェクトを作成

とりあえず既にあるという前提です。

```sh
$ tree
.
├── CMakeLists.txt
└── src
    ├── factorial.cpp
    ├── factorial.h
    └── main.cpp
```

```CMake:CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(example_googletest_on_cmake)

set(CMAKE_CXX_STANDARD 14)

add_executable(main src/main.cpp src/factorial.cpp)
```

この方法は`FetchContent_MakeAvailable()`コマンドを使うため、CMake3.14以上が必要です。それより古いCMakeを使う場合は、前回の記事「[CMakeプロジェクトでgoogletest](https://qiita.com/utkamioka/items/c931e9726d06cdf4edb7)」を参照してください。

`main.cpp`や`factorial.{cpp,h}`の内容は省きます。ファイル名から適当にお察しください。
今回は`factorial.cpp`がテストしたいファイルになります。

## 手順1: 既存の`CMakeLists.txt`に設定を追加

既存の`CMakeLists.txt`の末尾に、
https://github.com/google/googletest/tree/main/googletest#build-with-cmake
に記載された以下の内容を追記してください。

ただし、最後の数行は既存プロジェクトに合わせた書き換えが必要になります。詳しくは次の手順で説明します。

```cmake:CmakeLists.txt
include(FetchContent)
FetchContent_Declare(
  googletest
  # Specify the commit you depend on and update it regularly.
  URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

# Now simply link against gtest or gtest_main as needed. Eg
add_executable(example example.cpp)
target_link_libraries(example gtest_main)
add_test(NAME example_test COMMAND example)
```

なお、`609281088cfefc76f9d0ce82e1ff6c30cc3591e5`は適宜、最新のコミットidに書き換える必要があります。

## 手順2: `CMakeLists.txt`をプロジェクトに合わせて書き換え

手順1で追記したgoogletestの設定内容を、現在のプロジェクトに合わせて書き換えます。
書き換えが必要なのは最後の3行だけです。

```cmake:CMakeLists.txt(書き換え前)
# Now simply link against gtest or gtest_main as needed. Eg
add_executable(example example.cpp)
target_link_libraries(example gtest_main)
add_test(NAME example_test COMMAND example)
```

```cmake:CMakeLists.txt(書き換え後の例)
add_executable(${PROJECT_NAME}-googletest src/factorial.cpp test/test_factorial.cpp)
target_link_libraries(${PROJECT_NAME}-googletest gtest_main)
target_include_directories(${PROJECT_NAME}-googletest PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)

add_test(NAME test COMMAND ${PROJECT_NAME}-googletest)
enable_testing()
```

### `add_executable()`

* https://cmake.org/cmake/help/latest/command/add_executable.html
* 生成するgoogletestの実行ファイル（`${PROJECT_NAME}-googletest`）と、
  リンクするファイル（テストコードとテスト対象のコードを含むファイル）を列挙します。
* 実行ファイル名は変更せず`example`のままでも大丈夫ですが、、、まぁ普通に`example`は無いですよね。
* テストコードはtestディレクトリに配置（`test/test_factorial.cpp`）するようにしています。
* `main()`関数はgoogletestに含まれる方を使いますので、
  実行ファイルをビルドするようなプロジェクトの場合、`main()`を含むファイルは除外してください。
* 言うまでもありませんが、
  実際のプロジェクトでは大量のファイル名を何度も記述することになるので、変数に格納して使い回すのが定石です。

### `target_link_libraries()`

* https://cmake.org/cmake/help/latest/command/target_link_libraries.html
* googletestの`main()`を含むライブラリ`gtest_main`をリンクします。
* ターゲットファイル名は`add_executable()`の記述に合わせて書き換えてください。

### `target_include_directories()`

* https://cmake.org/cmake/help/latest/command/target_include_directories.html
* 通常、テストコードの中でテスト対象の関数を呼び出すには、それを宣言したヘッダファイルをincludeする必要があります。
  ですので、ヘッダファイルが配置してあるディレクトリを記述してください。
* この例は、全てのヘッダファイルがsrcディレクトリに配置してあるという仮定に基づいた設定です。
  ライブラリをビルドするプロジェクトの場合、公開するAPIについては個別にincludeディレクトリなどに置くケースもあるはずです。
  その場合は適宜記述を変更してください。
* ターゲットファイル名は`add_executable()`の記述に合わせて書き換えてください。

### `add_test()`

* https://cmake.org/cmake/help/latest/command/add_test.html
* `ctest`経由で実行されるテストを記述します。
* `COMMAND`には`add_executable()`に記述したターゲットファイル名を指定してください。
* `NAME`は、複数のテストを構成する場合には、
  個別にユニークな名前を指定する必要があるそうですが、今回は1個だけなので何でも良いです。
  （ドキュメントに記述が見つかりませんでしたが、
  `add_test()`の記述が1個だけの場合はNAMEの宣言すら必要ありませんでした。）
* `ctest`を使わずコマンドラインから直接実行してもテスト可能なので、`add_test()`は無くても構いません。

### `enable_testing()`

* https://cmake.org/cmake/help/latest/command/enable_testing.html
* `ctest`でテストを実行する場合には`add_test()`と合わせて宣言します。
* 他にも、生成される`Makefile`にtestというルールを追加する機能もあるようです。

## 手順3: テストを書く＆実行する

本題（CMakeプロジェクトへのgoogletestの組み込み方法）とは別の話なので、簡単に。

### テストコード

googletestの使い方は[「Google Test ドキュメント日本語訳」](http://opencv.jp/googletestdocs/index.html)で丁寧に説明されています。
「上級ガイド」や「よくある質問」も役に立ちそうな情報がてんこ盛りなので、一通り目を通しておきましょう。

```c++:test/test_factorial.cpp
#include <stdexcept>
#include "gtest/gtest.h"

#include "factorial.h"

TEST(test_factorial, positive_values) {
    EXPECT_EQ(1, factorial(1));
    EXPECT_EQ(2, factorial(2));
    EXPECT_EQ(6, factorial(3));
}

TEST(test_factorial, zero) {
    EXPECT_EQ(1, factorial(0));
}

TEST(test_factorial, negative_value) {
    EXPECT_THROW(factorial(-1), std::invalid_argument);
}
```

### ビルド

普通にCMakeのビルドと同じです。

```sh
$ mkdir build
$ cd build
$ cmake ..
$ make all
```

### テスト実行

`add_executable()`に記述したターゲットファイル名を実行するだけです。
`add_test()`と`enable_testing()`を記述してある場合は、`ctest`や`make test`でも実行可能です。
[「テストプログラムを実行する：高度なオプション」](http://opencv.jp/googletestdocs/advancedguide.html#adv-running-test-programs-advanced-options)も参考にしてください。

```sh
$ ./example_googletest_on_cmake-googletest
Running main() from /any/where/example-googletest-with-cmake/build/_deps/googletest-src/googletest/src/gtest_main.cc
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from test_factorial
[ RUN      ] test_factorial.positive_values
[       OK ] test_factorial.positive_values (0 ms)
[ RUN      ] test_factorial.zero
[       OK ] test_factorial.zero (0 ms)
[ RUN      ] test_factorial.negative_value
[       OK ] test_factorial.negative_value (0 ms)
[----------] 3 tests from test_factorial (1 ms total)

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

# おわりに

[前回](https://qiita.com/utkamioka/items/c931e9726d06cdf4edb7)の内容と比べると手順も減ったので、普段からCMakeを使っている人には簡単すぎる内容だったと思います。

つまり最初からCMakeで運用していれば、ユニットテストを組み込むのは「簡単」だと言うことです。
`Makefile`を駆使した謎ビルドシステムを構築したり、`build.sh`でお茶を濁したりせず、定番（＝CMake）を導入した方が結果的に低コストに運用できると思います。