はじめに
CMakeにはCTestというテストランナーがある。
CTestはテスト用の実行ファイル(テストバイナリ)が1つのテストとして認識される。
GoogleTest(GTest)のようなテストフレームワークの場合、1つのテストバイナリに複数のテストケースが含まれることが常となる。
そこでCMakeにはGTestのテストが一つのCTestとして扱われるようにするためのサポート機能がある。
テスト対象のプログラム
CMake C++でユニットテスト入門(初級編)で作成した、なんの役にも立たないテストプログラムを使用する。
使用するのは下記のファイル。
- hello.cpp
- hello.h
GTest
aptでインストール
ExternalProject
とかいろいろあるが、まずそれ以外のことを試したいので、googletestのパッケージをaptでインストールする。
$ sudo apt install -y googletest
ちなみに作業環境はUbuntu 20.04
テスト
このサンプルプログラムに対するGoogleTestを以下のように作ってみる。
#include "gtest/gtest.h" #include "hello.h" #include <stdexcept> Hello h; /* This block will uncomment after. TEST(HelloTest, NullPtr) { EXPECT_THROW(h.hello(nullptr), std::runtime_error); } */ TEST(HelloTest, default_param) { EXPECT_EQ(h.hello(), "empty"); } TEST(HellsoTest, empty_string) { EXPECT_EQ(h.hello(""), "empty"); } TEST(HelloTest, normal_case) { EXPECT_EQ(h.hello("John Doe"), "Hello John Doe"); }
NullPtr
のテストはあえてコメントアウトしておく。
CMakeLists.txt
まずは、テストランナーを使用せずに、GTestをリンクしたテストバイナリを生成する。
cmake_minimum_required(VERSION 3.10) # To use the googletest find_package(GTest REQUIRED) # Add the executable for the testcase which is using googletest add_executable(test_hello test_hello.cpp hello.cpp) target_link_libraries(test_hello GTest::GTest GTest::Main)
テストバイナリのビルド
この時点でディレクトリ構成は次のようになっている。
. ├── CMakeLists.txt ├── hello.cpp ├── hello.h └── test_hello.cpp
次のようにしてビルドする。
$ mkdir build && cd build $ cmake .. # make -j $(nproc)
テストバイナリの実行
$ ./test_hello Running main() from /home/mickey/work/trash/googletest-release-1.10.0/googletest/src/gtest_main.cc [==========] Running 3 tests from 1 test suite. [----------] Global test environment set-up. [----------] 3 tests from HelloTest [ RUN ] HelloTest.default_param [ OK ] HelloTest.default_param (0 ms) [ RUN ] HelloTest.empty_string [ OK ] HelloTest.empty_string (0 ms) [ RUN ] HelloTest.normal_case [ OK ] HelloTest.normal_case (0 ms) [----------] 3 tests from HelloTest (0 ms total) [----------] Global test environment tear-down [==========] 3 tests from 1 test suite ran. (0 ms total) [ PASSED ] 3 tests.
3つのテストがPASSしている。
ここまでで下準備完了。
gtest_add_tests
gtest_add_testsを試す。この機能はCMake 3.1の時点で追加されている。
今の形になったのは3.9の頃らしい。
CMakeLists.txtの修正
次のようにしてgtest_add_tests
を使用してみる。
cmake_minimum_required(VERSION 3.10) project(hello) # Enable the testing features. enable_testing() # To use the googletest find_package(GTest REQUIRED) # Enable the GoogleTest integration. include(GoogleTest) # Add the executable for the testcase which is using googletest add_executable(test_hello test_hello.cpp hello.cpp) target_link_libraries(test_hello GTest::GTest GTest::Main) # Add the test case use the old feature. gtest_add_tests(TARGET test_hello)
ctestの実行
ctestでテストを実行してみる。
$ ctest Test project /home/mickey/work/c_lang/gtest/build Start 1: HelloTest.NullPtr 1/4 Test #1: HelloTest.NullPtr ................ Passed 0.00 sec Start 2: HelloTest.default_param 2/4 Test #2: HelloTest.default_param .......... Passed 0.00 sec Start 3: HelloTest.empty_string 3/4 Test #3: HelloTest.empty_string ........... Passed 0.00 sec Start 4: HelloTest.normal_case 4/4 Test #4: HelloTest.normal_case ............ Passed 0.00 sec 100% tests passed, 0 tests failed out of 4 Total Test time (real) = 0.01 sec
コメントアウトされているはずのNullPtr
テストが実行されている。
gtest_add_testsの弱点
一つのテストバイナリを指定すると、GTestのテストケースに対応してGTestを実行してくれるが、下記の問題点をはらんでいる。
- テスト抽出のタイミングがcmake実行時
- 文字列ベースでテストを抽出するため、コメントアウトされていることを区別しない
つまり、Cコンパイラは当然のように知っているコメント行や#if 0
などは感知されないため、TEST
マクロの行を愚直に検出してしまう。
また、テストの検出がcmake実行時であるため、テストケースを追加したり削除したりする場合、cmakeから実行し直す必要がある。
gtest_discover_tests
gtest_discover_testsを試す。この機能は3.10で追加されている
CMakeLists.txtの修正
次のようにしてgtest_discover_tests
を使用してみる。
cmake_minimum_required(VERSION 3.10) project(hello) # Enable the testing features. enable_testing() # To use the googletest find_package(GTest REQUIRED) # Enable the GoogleTest integration. include(GoogleTest) # Add the executable for the testcase which is using googletest add_executable(test_hello test_hello.cpp hello.cpp) target_link_libraries(test_hello GTest::GTest GTest::Main) # Add the test case use the gtest feature. gtest_discover_tests(test_hello)
ctestの実行
ctestでテストを実行してみる。
$ ctest Test project /home/mickey/work/c_lang/gtest/build Start 1: HelloTest.default_param 1/3 Test #1: HelloTest.default_param .......... Passed 0.00 sec Start 2: HelloTest.empty_string 2/3 Test #2: HelloTest.empty_string ........... Passed 0.00 sec Start 3: HelloTest.normal_case 3/3 Test #3: HelloTest.normal_case ............ Passed 0.00 sec 100% tests passed, 0 tests failed out of 3 Total Test time (real) = 0.01 sec
コメントアウトされているNullPtr
テストは実行されていない。
NullPtrコメントの削除
NullPtrのテストをアンコメントして実行してみる。
$ make $ ctest Test project /home/mickey/work/c_lang/gtest/build Start 1: HelloTest.NullPtr 1/4 Test #1: HelloTest.NullPtr ................ Passed 0.00 sec Start 2: HelloTest.default_param 2/4 Test #2: HelloTest.default_param .......... Passed 0.00 sec Start 3: HelloTest.empty_string 3/4 Test #3: HelloTest.empty_string ........... Passed 0.00 sec Start 4: HelloTest.normal_case 4/4 Test #4: HelloTest.normal_case ............ Passed 0.00 sec 100% tests passed, 0 tests failed out of 4 Total Test time (real) = 0.01 sec
cmakeせずに、makeでテストバイナリをビルドし直すだけでNullPtrのテストも実行されるようになった。
gtest_discover_testsの強み
つまり、コメントや#if 0
などははプリプロセッサやコンパイラのルールに従って適切に処理されるため、明示的に実行したくないテストに関して実行されてしまうことがない。
また、テストの抽出がビルド時であるため、テストケースを追加したり削除したりする場合、cmakeから実行する必要がない。
まとめ
CMakeにはCTestとGoogleTestをうまく強調するための機能が提供されている。
gtest_add_tests
とgtest_discover_tests
があるが、 問答無用でgtest_discover_testsの方を使うべき
日本語の情報で検索するとgtest_add_testsが出てくる場合が多いので注意が必要。