最新の情報に合わせて新規記事を記述しました。
https://qiita.com/utkamioka/items/cacb1001bd2abf605b15 を参照してください。
はじめに
CMakeで構成されたC++のプロジェクトに、googletestによるユニットテストを組み込む方法です。
公式で説明されている方法の一つ("Incorporating Into An Existing CMake Project")を改めて解説し直すだけですが、C++もCMakeも知らないエンジニアが、なぜかマネージャーとしてC++のプロジェクトをレビューせざるを得なくなった場合に、せめてテスト環境だけでも整えるため、最小限の手順でユニットテストに対応するための覚書として残しておきます。
この方法のメリット
- OSが持つgoogletestライブラリに依存しないので、
- (yumやaptのための)管理者権限が不要です。
- 誰が、どこで(どのOSで)実行しても、安定した結果が得られます。
- ビルドの実行ごとにgoogletestのソースコードをcloneするので、
- 最新のgoogletestを利用できます(バージョンを指定することも出来ます)。
- ソースリポジトリを汚さず、必要最小限のものだけでプロジェクトを構成できます。
手順
手順0: 普通のCMakeプロジェクトを作成
とりあえず既にあるという前提です。
よく知りませんが、イマドキ(?)のC++のビルドツールはCMakeで良いんですよね?それとも、もっと良いものがあるんでしょうか?
$ tree
.
├── CMakeLists.txt
└── src
├── factorial.cpp
├── factorial.h
└── main.cpp
cmake_minimum_required(VERSION 3.0)
project(example_googletest_on_cmake)
set(CMAKE_CXX_STANDARD 14)
add_executable(main src/main.cpp src/factorial.cpp)
今回紹介する方法は「CMakeのバージョンが2.8.2以上」ですので、それ以上であればcmake_minimum_required()
は何でも良いはずです。
main.cpp
やfactorial.{cpp,h}
の内容は省きます。ファイル名から適当にお察しください。
今回はfactorial.cpp
がテストしたいファイルになります。
手順1: CMakeLists.txt.in
を作成
CMakeLists.txt
と同じディレクトリにCMakeLists.txt.in
を作成します。
内容は https://github.com/google/googletest/tree/master/googletest#incorporating-into-an-existing-cmake-project と同じで構いません。
cmake_minimum_required(VERSION 2.8.2)
project(googletest-download NONE)
include(ExternalProject)
ExternalProject_Add(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG main
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src"
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)
リンクするgoogletestのバージョンを固定したい場合は、GIT_TAG
にタグやコミットIDを指定してください。
手順2: 既存のCMakeLists.txt
に設定を追加
既存のCMakeLists.txt
に、手順1で作成したCMakeLists.txt.in
を読み込んだり、諸々の設定を追加します。
基本的には https://github.com/google/googletest/tree/master/googletest#incorporating-into-an-existing-cmake-project に記載された以下の内容を追記するだけです。
ただし、最後の数行は既存プロジェクトに合わせた書き換えが必要になります。詳しくは次の手順で説明します。
# Download and unpack googletest at configure time
configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
RESULT_VARIABLE result
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download )
if(result)
message(FATAL_ERROR "CMake step for googletest failed: ${result}")
endif()
execute_process(COMMAND ${CMAKE_COMMAND} --build .
RESULT_VARIABLE result
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download )
if(result)
message(FATAL_ERROR "Build step for googletest failed: ${result}")
endif()
# Prevent overriding the parent project's compiler/linker
# settings on Windows
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
# Add googletest directly to our build. This defines
# the gtest and gtest_main targets.
add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src
${CMAKE_CURRENT_BINARY_DIR}/googletest-build
EXCLUDE_FROM_ALL)
# The gtest/gtest_main targets carry header search path
# dependencies automatically when using CMake 2.8.11 or
# later. Otherwise we have to add them here ourselves.
if (CMAKE_VERSION VERSION_LESS 2.8.11)
include_directories("${gtest_SOURCE_DIR}/include")
endif()
# 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)
手順3: CMakeLists.txt
をプロジェクトに合わせて書き換え
手順2で追記したgoogletestの設定内容を、現在のプロジェクトに合わせて書き換えます。
書き換えが必要なのは最後の3行だけです。
# 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)
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というルールを追加する機能もあるようです。
手順4: テストを書く&実行する
本題(CMakeプロジェクトへのgoogletestの組み込み方法)とは別の話なので、簡単に。
テストコード
googletestの使い方は「Google Test ドキュメント日本語訳」で丁寧に説明されています。
「上級ガイド」や「よくある質問」も役に立ちそうな情報がてんこ盛りなので、一通り目を通しておきましょう。
#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);
}
脱線になりますが、googletestにはwhat()
の内容をテストできるAssertionは無いのでしょうか?try-catchしないとダメ?
ビルド
普通にCMakeのビルドと同じです。
$ mkdir build
$ cd build
$ cmake ..
$ make all
テスト実行
add_executable()
に記述したターゲットファイル名を実行するだけです。
add_test()
とenable_testing()
を記述してある場合は、ctest
やmake test
でも実行可能です。
「テストプログラムを実行する:高度なオプション」も参考にしてください。
$ ./example_googletest_on_cmake-googletest
Running main() from /any/where/build/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 (1 ms)
[----------] 3 tests from test_factorial (7 ms total)
[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (14 ms total)
[ PASSED ] 3 tests.
おわりに
「なんか臭い」けど「C++の知識も経験もない」ので「ここ本当に正しい?」と消極的な指摘しか出来ない状態でしたが、ユニットテストを素早く回せる環境があることで「こういうふうに使うと期待した動作にならないので確認してね(ニッコリ)」と具体的なコードで殴り合い^H^H^H^H対話ができるようになりました。
手に負えそうにない技術をマネジメントする場合には、まず真っ先に、使い物になるユニットテスト環境を整備し、ユニットテストをとっかかりにエンジニア目線で参加するのがオススメです。