Boost 逆引きリファレンスのサンプルコードをテスト化してみる

この記事は C++ Advent Calendar 2013 の 8 日目の記事になります(あれ、俺 8 日目なのか……)。このネタを思いついたのは、先月に boostjp の ML で見かけた以下の投稿からでした。

Boost 1.55.0 Beta 1 RCが公開されました。
http://lists.boost.org/Archives/boost/2013/10/206792.php
来週頭にはBoost 1.55.0 Beta 1がリリース、今月末のBoost 1.55.0 RCを経て、11月第1週にはBoost 1.55.0のリリースとなるかと思います。
# ちなみにそういったスケジュールは http://www.boost.org/community/ のカレンダーに書いてあります。
1.55.0リリースしてから問題があっても遅いので、今のうちからテストしたりしてもらえると非常に助かります。

https://groups.google.com/forum/#!topic/boostjp/Xub5TE3VCPM

この投稿を見ながら(投稿内容とはちょっとずれていますが)「あぁ、バージョンアップの度に、サンプルコードがきちんと動作をするかどうかもテストした方が良いのか」と思い、「Boost 逆引きリファレンスのサンプルコード、テスト化計画」を密かに始めてみました。

clown/boostjp では、Boost 逆引きリファレンス に掲載されている各種サンプルコードを出来るだけ原型を保ったままテスト化すると言う試みを行っています。例えば、配列 - boostjp の最初のサンプルコード

#include <iostream>
#include <boost/array.hpp>
#include <algorithm>

void disp(int x) { std::cout << x << ' '; }

int main()
{
    boost::array<int, 3> ar = {3, 1, 4};
    std::for_each(ar.begin(), ar.end(), disp);
}

これを以下のようにテスト化します。

#include <boost/test/unit_test.hpp>
#include <boost/array.hpp>
#include <algorithm>
#include <sstream>
#include "ostream_proxy.h"

BOOST_AUTO_TEST_CASE(test_overview) {
    boost::array<int, 3> ar = { 3, 1, 4 };

    std::ostringstream ss;
    std::for_each(ar.begin(), ar.end(), ostream_proxy(ss));
    BOOST_CHECK_EQUAL(ss.str(), "3 1 4 ");
}

「標準出力(std::cout)へ出力」と言うサンプルコードのテスト方法

サンプルコードの特徴として、標準出力(std::cout)に出力して結果を確かめる(掲載する)と言うコードが多くなります。特に、前述した例のように「アルゴリズムで指定した関数の中で std::cout に出力する」と言うサンプルコードの場合、どうやってテスト化すべきか悩んだのですが、最終的には以下のような補助クラスを用いて std::cout の代わりに std::ostringstream に出力させ、最後に出力した文字列で比較すると言う形を取る事にしました。

class ostream_proxy {
public:
    explicit ostream_proxy(std::ostream& stream, char separator = ' ') :
        stream_(stream), separator_(separator) {}

    template <class T>
    void operator()(const T& value) const {
        stream_ << value << separator_;
    }
}

本当はもう少し完成した状態で記事を書く予定だったのですが、予想以上に手間取ってしまったため現時点でひとまず記事として公開します。static assert(コンパイル時にエラーを発生させるサンプルコード)等いくつかはテスト化できそうにないものもありますが、掲載されているサンプルコードはできるだけを網羅してみようかと思います。

Related Pages

広告を非表示にする

Boost.Test (boost/test/included/unit_test.hpp) の使い方

リンク無しでテスト用のソースファイルが複数ある状況でビルドしようとしたら嵌ったお話。

先日、ユニットテストについての講義をする際に「取りあえず Boost.Test を使ってみましょう」と言うことで、リンク(≒ Boost をビルド)しなくても良い boost/test/included/unit_test.hpp を使おうとしたのですが、以下のようなリンクエラーが出力されました(Visual C++ 2012)。

error LNK2005: "public: __thiscall 
boost::unit_test::ut_detail::auto_test_unit_registrar::auto_test_unit_registrar(class 
boost::unit_test::test_unit_generator const &)" (??
0auto_test_unit_registrar@ut_detail@unit_test@boost@@QAE@ABVtest_unit_generator@23@@Z)
は既に gp2-test.obj で定義されています。

…(後略)…

ビルドを通すのにやや苦労しましたが、結論としては「リンク無しで Boost.Test フレームワークを複数のソースファイルが存在する状況で使用する場合」は以下の点を注意する必要があるようです。

  1. #include <boost/test/included/unit_test.hpp> を記述するのは 1 箇所のみ(2 箇所以上でインクルードすると、前述したリンクエラーが発生)。main.cpp 的なファイルを作成して、そこでインクルードするのが良いか。
  2. それ以外のファイル(各テストを記述したファイル)については、リンクしない場合であっても
    #include <boost/test/unit_test.hpp> と記述する。
  3. このままだと Boost.Test 用のライブラリをリンクしようとしてエラーになるので、コンパイル時に BOOST_TEST_NO_LIB または BOOST_ALL_NO_LIB を定義してリンクしないようにする。

ソースファイルの構成例

1. main.cpp

#define BOOST_TEST_MAIN // or #define BOOST_TEST_MODULE test_module_name
#include <boost/test/included/unit_test.hpp>

2. test1.cpp

#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_CASE(test_case1) {
    BOOST_CHECK(true);
}

3. test2.cpp(以下、必要なファイル数だけ同様に)

#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_CASE(test_case2) {
    BOOST_CHECK(true);
}

コンパイル時に BOOST_TEST_NO_LIB 指定を忘れずに。気を付けないと嵌りますが、リンク有無に関わらずメインファイル以外のインクルード記述は変わらないので、どちらにも移行しやすいと言うのは利点かもしれません。

広告を非表示にする

C++ のテストフレームワークを選ぶポイント

Boost.勉強会 #8 大阪 に参加してきました。発表者、運営者の皆様お疲れ様でした。今回もいろいろと黒魔術的な発表があり「これが変態かー」と感慨深く見ていたのですが、それはそれとして、個人的に気になっていたテーマとして「C++ でのテスト」に関連する発表が 2 件ほどあったので、楽しみにしていました。

C++ のテストフレームワークも数多く公開されており、好みや用途によって選択肢が変わって来るのですが、まぁ今だと選択肢として挙がるのは「Boost.Testgoogletest か」になってくるのかなと思います。どちら(もしくは、上記以外を含めたどれか)を選択すべきか、と言う問いに対しては、残念ながら現状でも決め手となるものに欠けているのですが、自分が利用する上で選択する上でのポイントを少し検討してみます。

手軽に利用できる

発表中でもありましたが、Boost.Test には「ヘッダファイルのインクルードのみで利用可能なテストフレームワーク」と言うものが用意されており、比較的小さなライブラリ等のプログラムをテストする際には便利でよく利用しています(参考:Boost minimal test - Faith and Brave - C++で遊ぼう)。

ビルドしてリンクする位どうって事ないと感じたりもするのですが、特に、Windows (Visual C++) 上で開発している時には /MT、/MD の違いや x86/x64 の違いで予想外にリンクに手こずる事もあり、リンク作業を敬遠しがちになったりもします。そう言った事もあって「ヘッダファイルのインクルードのみで利用可能」と言うのは魅力的に感じる時も多々あり、boost/test/minimal.hpp にはよくお世話になっています。

部分的にテスト可能

C++ Unit Test Frameworks - ACCU を読んでいて思い出したのですが、「部分的にテスト可能かどうか」と言う点も選択肢を検討する上でポイントになったりします。例えば、Microsoft .NET Framework 系?のプログラムのテストフレームワークである NUnit だと下記のように、テストしたい部分のみを選択して実行できるようになっています。

部分的にテストを行える機能があると、ネットワーク通信用のライブラリ等の「時間のかかる」テストも「テーストハーネス内に記述はするが、通常時(何度も実行する時)は実行されないようにしておく」のような選択肢を取る事もできます。

単体テストは速く走る。速く走らないとしたら、それは単体テストではない。他の種類のテストが単体テストの仮面をかぶっていることもよくある。次に当てはまるものは単体テストではない。

  1. データベースとやり取りをする
  2. ネットワークを介した通信をする
  3. ファイルシステムにアクセスする
  4. 実行するために特別な環境設定を必要とする(環境設定ファイルの編集など)

上記に該当するテストが悪いというわけではない。多くの場合において、そのようなテストを書く価値はあり、しばしばテストハーネス内に記述される。しかし単体テストは、そのようなテストと切り分けて、変更を行うたびに高速で実行できるように保ち続けることが重要である。

レガシーコード改善ガイド(p.17)

部分的にテストを行う機能については、ざっと見たところ Boost.Test、googletest ともに可能なようです。

ディフォルトでは,Google Testはユーザが定義したすべてのテストを実行します.しかし,ときにはテストの一部だけを実行したい場合もあるでしょう(たとえばデバッグや変更の速やかな確認のために).GTEST_FILTER環境変数か,--gtest_filterフラグをセットすることで,このフィルタに一致するテストだけを実行することが可能となります(形式は,TestCaseName.TestNameです)

GoogleTestAdvacnedGuide(翻訳)

これをひとまとめにしてテストされるプログラムも参照可能にしてコンパイルすると、すべてのテストを実行してくれるテストプログラムができる。WARNの表示や一部のテストだけを実行したいときは、コマンドラインで引数を指定すればよい。

(例)
    --log_level=warning (エラーレベルがWARNのところも表示)
    --run_test=suite名,suite名/test名 (特定のテストだけ実行)
Boost Testメモ

CppUnit と CppUnitLite

最後に蛇足的な話題。C++ のテストフレームワークと言うと CppUnit もよく名前が挙がるのですが、もう少し簡単に記述できるテストフレームワークとして CppUnitLite と言うものがあるそうです。

最初に CppUnit を開発した時、私はできるだけ JUnit に近いものにしようと考えました。そのほうが xUnit のアーキテクチャを見たことがある人にとって使いやすいだろうと考えたからです。しかしすぐさま、C++ ではきれいに実装することが難しい、あるいは不可能な事柄が次々に出てきました。これは C++Java の機能の違いによるものです。一番の問題は C++ にリフレクションの機能がないことでした。Java では、派生クラスのメソッドに対する参照を持って、実行時にメソッドを検索するといったことが可能です。しかし C++ では、実行時にアクセスすべきメソッドを登録するコードは人間が書いてやる必要があります。その結果、CppUnit は少し使いにくく理解づらいものになりました。

・・・(中略)・・・

CppUnit も CppUnitLite もテストハーネスとして利用可能です。CppUnitLite を使って書いたテストのほうが少し短いため、本書の C++ の例では CppUnitLite を使用しています。

レガシーコード改善ガイド(p.57-59)
広告を非表示にする