CMake: CTest


はじめに

みなさん、こんにちは。今回は CTest について書いていきます。

CTest とは何か?

CTest とは、テストの実行を支援する、いわゆるテストランナーのことで、テストを実行するコマンドを複数登録し、それぞれ実行するというシンプルなものです。しかし、実行するテストの選択・グループ化・並列実行など便利な機能を備えており、テスティングフレームワークや言語を問いません。このため、すべてのテストをCTestを通して行うようにすれば、テストの実行方法が共通化され、テストランナー機能が貧弱なテスティングフレームでのテストも便利に実行することが可能になります。

CTest でのテストは、CMakeLists.txtにテストの設定を記述し、ビルドディレクトリにてctestコマンドを実行することにより行います。そのほかにも、CDash といわれるテストサーバーとの連携や、スクリプトファイルによる高度なテスト実行が可能になっているようです。

CTest の有効化

さて、CTest を有効にするためには、enable_testing()コマンドを利用する必要があります。このコマンドは、プロジェクトルートで実行する必要があり、実行すると各ビルドディレクトリにCTestTestfile.cmakeというテスト用のファイルができます。

CMakeLists.txt
cmake_minimum_required(VERSION 3.0.2)
enable_testing()
$ cmake .
project-root/
├── CTestTestfile.cmake
├── ...
└── subdir/
       ├── CTestTestfile.cmake
       └── ...

テストの追加

次に、テストを登録していきます。テストの登録にはadd_test()コマンドを使用します。定義は下記のとおりになります。

add_test(NAME <name> COMMAND <command> [<arg>...]
         [CONFIGURATIONS <config>...]
         [WORKING_DIRECTORY <dir>])

add_test — CMake 3.0.2 Documentation

各引数の意味はそれぞれ下記のようになっています。

NAME

<name>にはテスト名を指定します。これはテストの識別子となりますので重複することはできません。

COMMAND

<command><arg>...にはテストを実行するコマンドとその引数を指定します。これらには generator expressions を使用することができます。

CONFIGURATIONS

テスト実行時のテスト構成(Release など)が<config> ...で指定した値のいずれかの時のみ実行します。

WORKING_DIRECTORY

<dir>にはテストコマンドの実行ディレクトリを指定します。初期値は、ビルドディレクトリのルートです。また、generator expressions が使用できます。


例えば、以下のように使用します。

add_executable(unit_test hoge_test.cpp foo_test.cpp) # テストを行う実行ファイル unit_test

add_test(
  NAME hoge_test # テスト名は hoge_test
  COMMAND $<TARGET_FILE:unit_test> --only=mylib::hoge # mylib::hoge というテストのみ実行する
  CONFIGURATIONS Release # テスト構成がReleaseのときのみ実行
  WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/tmp # 実行ディレクトリは、ビルドディレクトリ直下のtmpディレクトリ
)

テストの設定

add_test()コマンドで追加したテストは、それぞれプロパティを持っており、テストプロパティと呼ばれています。このテストプロパティは、テストの実行に関する設定を行うことができ、これによって柔軟な実行が可能となります。set_property(TEST)およびset_tests_properties()コマンドで設定できます。

set_property(TEST hoge_test PROPERTY LABELS hoge)
set_tests_properties(hoge_test PROPERTIES LABELS hoge)

このテストプロパティには様々ありますが、以下のプロパティをよく使うでしょう。

LABELS

プロパティにラベルとよばれるタグを付与することができます。これは、テストしたい実行ファイルを選択するときに使用することができます。また、テスト後にラベルごとに実行時間が集計されます。ラベルは複数持つことができ、リストとして格納されます。

DEPENDS

通常テストの実行順序は、add_test()コマンドで追加した順となりますが、このプロパティを設定すると、指定されたテストの実行が終了してからテストを実行するようになります。これには複数指定可能で、リストとして格納されます。

ENVIRONMENT

このテストでだけ有効な環境変数を設定します。これは<var-name>=<value>といった形で指定します。これには複数指定可能で、リストとして格納できます。

RUN_SERIAL

CTest は登録されたテストを並列に実行できますが、このプロパティを設定すると、指定したテストとは並列に実行しなくなります。これには複数指定可能で、リストとして格納されます。

FAIL_REGULAR_EXPRESSION

このプロパティを設定すると、テストの出力が指定した正規表現にマッチする場合は失敗、そうでない場合は成功と判断されます。

PASS_REGULAR_EXPRESSION

このプロパティは、FAIL_REGULAR_EXPRESSIONプロパティと似ていますが、指定した正規表現にマッチする場合は成功、そうでない場合は失敗となります。

SKIP_RETURN_CODE

このプロパティを設定すると、テスト実行コマンドの戻り値が指定した値だった場合は、テストをスキップしたと判断されます。

TIMEOUT

このプロパティを設定すると、テストの実行時間が指定した値を超えると強制的に終了するようになります。値の単位は秒です。


これ以外のプロパティに関しては、cmake-properties(7) — Properties on Tests を参照してください。

テストの実行

テストの実行は、ビルドシステムを生成した後に、ビルドディレクトリでctestコマンドを使用することで行います。

$ cmake .
$ ctest

また、ビルドシステムが Unix Makefile の場合は、Makefiletestというターゲットが追加されており、これを用いても実行することもできます。この時、ARGS変数に値を設定するとctestにオプションを渡せます。

$ cmake .
$ make test ARGS='-V'

ctestコマンドには様々なオプションがあり、実行するテストの選択や並列実行などを行うことができます。以下は、よく使うと思われるオプションです。

-V, --verbose

詳細実行モードです。このモードでは、テストの出力がすべて表示されます。

--output-on-failure

テストが失敗したときのみ、テストの出力を表示します。

-j <jobs>, --parallel <jobs>

テストを並列に実行します。<jobs>には、最大の並列数を指定します。CPU のコア数を指定するとよいでしょう。

-R <regex>, --tests-regex <regex>

テスト名が指定した正規表現<regex>にマッチするテストのみを実行します。

-E <regex>, --exclude-regex <regex>

テスト名が指定した正規表現<regex>にマッチするテスト以外を実行します。

-L <regex>, --label-regex <regex>

指定した正規表現<regex>にマッチするラベルに紐づくテストのみを実行します。

-LE <regex>, --label-exclude <regex>

指定した正規表現<regex>にマッチするラベルに紐づくテスト以外を実行します。

--no-label-summary

ラベルごとの実行時間の集計部分を表示しなくなります。

--timeout <seconds>

テストの実行時間が指定した値<seconds>を超えると強制的に終了するようになります。値の単位は秒です。このオプションとTIMEOUTプロパティが同時に指定されると、プロパティの方が優先されます。


これ以外のオプションに関しては、ctest(1) — Options を参照してください。

また注意点として、値をとるオプションを使う場合、短いオプションのオプション名と値の間にはスペースが必要となり、長いオプションのオプション名と値の間は=ではなくスペースとなります。

$ ctest -j4  # NG
$ ctest -j 4 # OK
$ ctest --parallel=4 # NG
$ ctest --parallel 4 # OK

CTest の使用例

以下は、テスティングフレームワークに Boost.Test を使用しているプロジェクトでの CTest の使用例です。

hoge.hpp
#ifndef MYLIB_HOGE_HPP
#define MYLIB_HOGE_HPP

namespace mylib {
    inline int hoge() {
        return 1;
    }
}

#endif
hoge_test.cpp
#define BOOST_TEST_MAIN
#include <boost/test/included/unit_test.hpp>
#include "hoge.hpp"

BOOST_AUTO_TEST_SUITE(mylib)
BOOST_AUTO_TEST_CASE(mylib) {
    BOOST_CHECK_EQUAL(hoge(), 1);
}
BOOST_AUTO_TEST_SUITE_END()
foo.hpp
#ifndef MYLIB_FOO_HPP
#define MYLIB_FOO_HPP

namespace mylib {
    inline int foo() {
        return 2;
    }
}

#endif
foo_test.cpp
#define BOOST_TEST_MAIN
#include <boost/test/included/unit_test.hpp>
#include "foo.hpp"

BOOST_AUTO_TEST_SUITE(mylib)
BOOST_AUTO_TEST_CASE(mylib) {
    BOOST_CHECK_EQUAL(foo(), 2);
}
BOOST_AUTO_TEST_SUITE_END()
CMakeLists.txt
cmake_minimum_required(VERSION 3.0.2)

find_package(Boost REQUIRED)
add_compile_options(-std=gnu++1y)
include_directories(${Boost_INCLUDE_DIRS})

enable_testing()

add_executable(hoge_test hoge_test.cpp)
add_test(NAME hoge COMMAND $<TARGET_FILE:hoge_test>)
set_property(TEST hoge APPEND PROPERTY LABELS ${PROJECT_SOURCE_DIR}/hoge.hpp)
set_property(TEST hoge APPEND PROPERTY LABELS ${PROJECT_SOURCE_DIR}/hoge_test.cpp)

add_executable(foo_test foo_test.cpp)
add_test(NAME foo COMMAND $<TARGET_FILE:foo_test>)
set_property(TEST foo APPEND PROPERTY LABELS ${PROJECT_SOURCE_DIR}/foo.hpp)
set_property(TEST foo APPEND PROPERTY LABELS ${PROJECT_SOURCE_DIR}/foo_test.cpp)
$ cmake .
$ make
$ ctest --no-label-summary
Test project ...
    Start 1: foo
1/2 Test #1: foo ..............................   Passed    0.01 sec
    Start 2: hoge
2/2 Test #2: hoge .............................   Passed    0.01 sec

100% tests passed, 0 tests failed out of 2

Total Test time (real) =   0.02 sec

Boost.Test のテストを CTest で実行することができました。ここで、各テストのLABELSプロパティに、関連するファイルのパスを設定しているのはテストを素早く行うためです。例えば、Vim でソースコードを編集しているときに、:!ctest -L % とすると関連するテストだけが実行されます(%は現在編集しているファイルパス)。

foo.hpp
:!ctest -L %
Test project ...
    Start 1: foo
1/1 Test #1: foo ..............................   Passed    0.02 sec

100% tests passed, 0 tests failed out of 1
...
hoge_test.cpp
:!ctest -L %
Test project ...
    Start 2: hoge
1/1 Test #2: hoge .............................   Passed    0.04 sec

100% tests passed, 0 tests failed out of 1
...

おわりに

以上、CTest でした。CTest を使うと柔軟にテストを実行できるので便利です。さらに、テスティングフレームワークや言語には依存していないのでテストの実行方法が共通化されます。

明日は、mrk_21 さんの『CMake: カスタムターゲットによるグループ化』です。