c++ doctest
使い方
ビルドシステムなし/単独ファイル
カレントディレクトリないし検索可能なディレクトリに doctest.h
があれば良い。
// clang++ lib.cpp -std=c++20
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest.h"
int add(int a, int b) { return a + b; }
TEST_CASE("testing the add function") { CHECK(add(1, 2) == 3); }
TEST_CASE("will fail") { CHECK(add(1, 2) == 0); }
これは以下のような出力をする
[doctest] doctest version is "2.4.9"
[doctest] run with "--help" for options
===============================================================================
lib.cpp:8:
TEST CASE: will fail
lib.cpp:8: ERROR: CHECK( add(1, 2) == 0 ) is NOT correct!
values: CHECK( 3 == 0 )
===============================================================================
[doctest] test cases: 2 | 1 passed | 1 failed | 0 skipped
[doctest] assertions: 2 | 1 passed | 1 failed |
[doctest] Status: FAILURE!
ユニーク(というかどうやってるんだろ)なのは、 ==
の左右の値が表示されること。ASSERT_EQ(a,b)
などのように、条件によってマクロを変える必要がない。
なお、この例はあまり現実的な使い方ではない。doctest が用意した main
関数が含まれるし、これはどうも -DDOCTEST_CONFIG_DISABLE
を設定しても、中身はないかもしれないが残っている。また、ファイル分割した時に一貫性がない。
ビルドシステムなし/複数ファイル
ファイル分割をしている場合。実際にアプリケーションやライブラリを作るパターン。ここではアプリケーションを作るパターンとする。
ファイルは以下の4つ
- lib1.cpp : ライブラリコードその1
- lib2.cpp : ライブラリコードその2
- test_main.cpp : テスト用のエントリーポイント
- main.cpp : アプリケーションエントリーポイント
#include "doctest.h"
int add(int a, int b) { return a + b; }
TEST_CASE("testing the add function") { CHECK(add(1, 2) == 3); }
重要な違いは、何もdefine していないこと。DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
は テスト用のライブラリの実装及びmain
を作る。DOCTEST_CONFIG_IMPLEMENT
はテスト用の実装 を作る。つまり、複数箇所に記述するとリンクエラーになる。何も指定しなければ宣言だけなのだろう。
lib2.cpp
と main.cpp
は省略するとして、test_main.cpp
は以下の通り。
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest.h"
テスト時は以下のようにビルドする。
clang++ lib1.cpp lib2.cpp test_main.cpp -std=c++20
非テスト時は以下のようにビルドする。
clang++ lib1.cpp lib2.cpp main.cpp -std=c++20 -DDOCTEST_CONFIG_DISABLE
DOCTEST_CONFIG_DISABLE
により、テスト周りのシンボルが消える。アプリケーションでもdoctestの機能を使いたい時(CHECK
などはテスト外でも便利に使える) は main.cpp
に以下を加え、DOCTEST_CONFIG_DISABLE
なしでビルドする。
#define DOCTEST_CONFIG_IMPLEMENT
#include "doctest.h"
meson
メタビルドシステムの meson を使う例。ソースファイルの記述は前述の ビルドシステムなし/複数ファイル
と特に変わらない。(src
に置いたりはしている)me
doctest 自体はシングルヘッダーで、ダウンロードすれば設定なしで使えるが、meson の wrap に登録されているのでそれを利用する。
mkdir subprojects # ないと失敗する
meson wrap install doctest # subprojects/doctest.wrap ができる
project('hello', 'cpp',
default_options: ['warning_level=3', 'werror=true', 'cpp_std=c++20'],
)
deps = [
dependency('doctest'),
]
includes = include_directories('include')
srcs = [
'src/hello/lib1.cpp',
'src/hello/lib2.cpp',
]
executable('hello',
srcs + ['src/main.cpp'],
include_directories: includes,
dependencies: [deps],
cpp_args: ['-DDOCTEST_CONFIG_DISABLE'],
)
test('hello test',
executable('hello_test',
src + ['src/hello/test_main.cpp'],
include_directories: includes,
dependencies: [deps],
)
)
非テスト時にも dependencies
に doctest を追加していることに注意。TEST_CASE
マクロなど、テストの機能を消すことそのものも doctest の機能の一部なので、doctest を入れた上で消す必要がある。それが嫌な場合は、テスト用の(gtest のように)ソースファイルを個別に作ってそちらに書く必要がある。そういったパターンでは以下のようになる。
project('hello', 'cpp',
default_options: ['warning_level=3', 'werror=true', 'cpp_std=c++20'],
)
test_deps = [
dependency('doctest'),
]
includes = include_directories('include')
srcs = [
'src/hello/lib1.cpp',
'src/hello/lib2.cpp',
]
executable('hello',
srcs + ['src/main.cpp'],
include_directories: includes,
)
test_srcs = [
'test/hello/test_lib1.cpp',
'test/hello/test_lib2.cpp',
'test/hello/test_main.cpp',
]
test('hello test',
executable('hello_test',
src + test_srcs,
include_directories: includes,
dependencies: [test_deps],
)
)
あるいは、ライブラリをターゲットとして作る場合は以下のようになる。(製品コードにテストを書く場合は、テストと非テスト時でビルドオプションを変える必要があるため、こういう分割はやり難い)
project('hello', 'cpp',
default_options: ['warning_level=3', 'werror=true', 'cpp_std=c++20'],
)
includes = include_directories('include')
hello_lib = library('hello',
[
'src/hello/lib1.cpp',
'src/hello/lib2.cpp',
],
include_directories: includes,
)
executable('hello',
['src/main.cpp'],
include_directories: includes,
link_with: hello_lib,
)
test('hello test',
executable('hello_test',
[
'test/hello/test_lib1.cpp',
'test/hello/test_lib2.cpp',
'test/hello/test_main.cpp',
],
include_directories: includes,
link_with: hello_lib,
dependencies: [dependency('doctest')],
)
)
cmake
meson と同じような感じなはず。気が向いたら書く。