しばらくブログを書けていなかったのでちょっと準備運動をする。比較的長めのものをこれから書こうと思っているからだ。
boost-ext/μtは、C++20用のテストライブラリだ。C++20らしくモジュールとしても使えるし、シングルヘッダオンリーライブラリとしても使える。
うれしいことにこれはCatch2と比べて非常にコンパイルが速く、Boost.UnitTestFrameworkと比べて非常に軽量だ。
https://github.com/boost-ext/ut#benchmarks
テストコードはラムダを使って書く。
#include <concepts> #include <iostream> #include <boost/ut.hpp> template<std::unsigned_integral UInt> constexpr UInt factorial(UInt x) { return (x == 0) ? 1 : x * factorial(x-1); } int main() { using namespace boost::ut::literals; // to use operator ""_test "factorial"_test = [] { boost::ut::expect(factorial(0u) == 1u); boost::ut::expect(factorial(1u) == 1u); boost::ut::expect(factorial(2u) == 2u); boost::ut::expect(factorial(3u) == 6u); boost::ut::expect(factorial(4u) == 24u); }; return 0; }
この""_test
は好きなところに書けるので、(コードと同じファイルにテストを書きたい、などの理由で)テストのためだけに.cpp
を用意したくない場合、headerであろうと書くことができる。そう、inline
変数がC++20には(C++17から)あるのだ。
// factorial.hpp namespace lib { template<std::unsigned_integral UInt> constexpr UInt factorial(UInt x) { return (x == 0) ? 1 : x * factorial(x-1); } } // lib #ifdef ACTIVATE_UNIT_TEST #include <boost/ut.hpp> inline boost::ut::suite test_factorial = [] { using namespace boost::ut::literals; // to use ""_test "factorial"_test = [] { boost::ut::expect(lib::factorial(0u) == 1u); boost::ut::expect(lib::factorial(1u) == 1u); boost::ut::expect(lib::factorial(2u) == 2u); boost::ut::expect(lib::factorial(3u) == 6u); boost::ut::expect(lib::factorial(4u) == 24u); }; }; #endif
こうしておけばmain.cpp
からinclude
されているものはすべて、ACTIVATE_UNIT_TEST
を定義している限り、テストが実行される。いやまあ、明らかにスケールしないのでファイルを分けてfactorial_test.cpp
みたいなものの中に書くべきだが、数ファイル以下の簡単なプロジェクトならこの程度から始めてもいいのではないか。気軽に行きましょう。
テストを有効化する際にマクロを使っているのは、せっかくmacro-freeなライブラリを使っているのに……という気持ちになるし、別にfactorial_test.cpp
みたいなやつ用意すればいいじゃないとも思うが、まあ。
ログを取りたいときはboost::ut::log
を使う。streamっぽいインターフェースで、改行が自動的に行われる。
boost::ut::suite test_factorial = [] { using namespace boost::ut::literals; // to use ""_test "factorial"_test = [] { boost::ut::log << "testing factorial() function ..."; boost::ut::expect(lib::factorial(0u) == 1u); boost::ut::expect(lib::factorial(1u) == 1u); boost::ut::expect(lib::factorial(2u) == 2u); boost::ut::expect(lib::factorial(3u) == 6u); boost::ut::expect(lib::factorial(4u) == 24u); }; };
テストが落ちたときに出力する内容は、expect
自体に流し込むとよい。こちらも
boost::ut::suite test_factorial = [] { using namespace boost::ut::literals; // to use ""_test "factorial"_test = [] { boost::ut::log << "testing factorial() function ..."; boost::ut::expect(lib::factorial(0u) == 1u) << "expecting 1 but got " << lib::factorial(0u); boost::ut::expect(lib::factorial(1u) == 1u) << "expecting 1 but got " << lib::factorial(1u); boost::ut::expect(lib::factorial(2u) == 2u) << "expecting 2 but got " << lib::factorial(2u); boost::ut::expect(lib::factorial(3u) == 6u) << "expecting 6 but got " << lib::factorial(3u); boost::ut::expect(lib::factorial(4u) == 24u) << "expecting 24 but got " << lib::factorial(4u); }; };
boost-extはほかにも様々な機能を持っているが、最低限これだけで使えないことはない。
ビルドが速いので、趣味でC++20を使えるプロジェクトでパパっとテストを書きたいときはかなりよい選択肢ではないだろうか。
boost-extはboost本体の一部ではないが、ほかにもいくつかライブラリを持っている。ちょっと見てみると面白いのではないか。