1. Google Testとは
Google Test はC/C++用のテストフレームワークです。
現在、githubの公式リポジトリでは、
- Google Test (gtest): 単体テストフレームワーク
- Google Mock (gmock): モッキングフレームワーク
の2つが同梱される形で配布されていますが、今回はこれらのうちgtestの使い方を調べます。
2. 目的
本記事は、簡単な単体テストが書けるようになるまでの個人的な勉強録です。
- 本記事では主に、Google Testを含むプロジェクトするビルドする方法や、CMakeとの連携方法に興味があります。
- Google Test関係の文法の説明は行いません。資料としては 公式チュートリアル や その日本語訳 があります。
3. 環境構築 (Google Testの準備)
以下の内容はmacOS Sierra (10.12.6) で試しています。CMakeのバージョンは3.11.0-rc1を使っていますが、より古いバージョンでも動く可能性はあります。
3.1. ビルドツール
CMake を使います。
大枠としては、下のようなプロジェクト構成にしたいです。
.
├── CMakeLists.txt
├── src // ここにソースコードを置きたい
└── test // srcのコードのテストを置きたい
それにあたり、次の2点が気になるので調べます。
- Google Testはどこに置けばよいか
- CMakeとGoogle Testをどう連携すればよいか (i.e.
CMakeLists.txt
をどう書くか)
2は1に依存します。つまり、Google Test自体をどこに置いたかで、CMakeがGoogle Testの依存関係をどうやって解決すればよいかが変わります。
3.2. Google Testをどこに置くか
当然テストもC++で書くので、CMakeがGoogle Testを無事見つけられる必要がありますが、どこに置くかが問題です。kaizoumanさんのブログによると、少なくとも3通りの方法があります:
- ビルド済みのGoogle Testをプロジェクト外部に置く
- Google Testごとプロジェクトに含めてしまう
- 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に対応する)。
(3) CMakeListの書き方
ここでは、CMakeからgtestを「探す」方法のみ書きます (使う方法は後述)。
(3.1) pkg-configを明示的に使う
CMakeには pkg-config用のモジュール が用意されているため、これを利用してgtestの場所を探すことができます。CMakeLists.txt
に次のような設定を書きます。
CMakeLists.txt
...
find_package(PkgConfig)
pkg_search_module(GTEST REQUIRED gtest_main)
...
うまくいけば GTEST_CFLAGS
や GTEST_LDFLAGS
などの変数群が使えるようになります。
参考:
(3.2) CMakeのFindモジュールを使う
CMakeの比較的新しいバージョンであれば、Google Testを 探すモジュール が用意されています。
CMakeLists.txt
...
find_package(GTest REQUIRED)
...
よくわかってないですが、内部的にはpkg-configを利用しているらしい (参考: 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 という機能があり、外部プロジェクトのコードを自動でダウンロードしてきてビルドするということができます。
結論からいうと、これをいい感じに隠蔽して使いやすくしたCMakeのモジュールが公開されているので、それを使います。
Crascit/DownloadProject -- Github
使い方はリンク先の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
- Unit testing with GoogleTest and CMake
- CMake ExternalProject 事始め
4. 簡単なテストの例
上記の3.1.1.と3.1.3.の方法を使って、簡単なテストをビルドします。
ここでは、ビルドできるかだけに興味があるため、公式のサンプルにならって、階乗を計算するだけのコードを書きます。
*/src/sample1.h
#ifndef SAMPLE1_H_
#define SAMPLE1_H_
int factorial(int n);
#endif
*/src/sample1.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
#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モジュール を使っています。特に、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