iglooとcmakeを使ったC言語でのBDDワークフロー


[サマリ]C/C++でBDDしたいならiglooとcmakeの組み合わせがオススメです

日本語だとびっくりするくらい情報が無いですが、Stackoverflowだとちょくちょく名前が出てきています
=> igloo - BDD Style Unit Testing for C++

xUnit系だとgoogletestがほぼ鉄板になりつつありそうな雰囲気がしますが、BDD系だと鉄板だといえるのが出ていない感あります。CSpec/CppSpecなどが候補に上がりますが、マクロがDSLとして作られすぎているとか、C言語のライブラリへのテストに向かないような仕様になっているものとかもあり。

そこで探していたところ、iglooがC言語でBDDするのにも対応できそうだし、DSLとしてのシンタックスも簡易で、何よりインストール不要でgit submoduleでブチ込めばすぐに使えるというのが気に入りました。

ついで、今までビルドには素直にmake使ってたのですが、ファイルの依存性管理とテスト用のビルド設定でMakefileがグチャグチャになりがちで、試しにcmakeを手習いがてら使ってみたらかなり捗る感じに開発が進むようになりました。

というわけでオススメします。

この記事は新規プロジェクトからigloo, cmakeを導入してBDDワークフローに至るまでの導入ドキュメントです。

(vimeo)igloo作者が投稿したライフゲームのBDDワークフロー

作者のJoakim Karlsson氏がvimeoに投稿したスクリーンキャストが分かりやすいです

=> (vimeo) C++ TDD Kata: Conway's Game of Life

gitと組み合わせてリポジトリのセットアップ

以下の手順はC言語でアプリケーション開発する、という前提です。

1.ディレクトリ階層

ふつうの構成です

project_root/
  |- include/ <- 開発する本体のヘッダファイル置き場 (*.h/*.hpp)
  |- extlib/ <- git submodule で管理する外部ライブラリとか
  |- src/ <- 実装コード置き場(*.c/*.cpp)
  |- t/ <- テストコード置き場 (*.cpp)
  |- bin/ <- 実行ファイル置き場、要 gitignore
  |- .gitignore
  |- CMakeList.txt
  |- README
  |- LICENSE
  \_ etc ...

そういえばC言語だとテスト用ディレクトリはなんかt一文字で済ますのをよく見かけるけど、これ慣例なのでしょうか

2.git initして.gitignoreに幾つかトラッキングしない設定を書く

他の.gitignoreな設定は省略しています。抜粋

# cmake artifacts
MakeFile
cmake_install.cmake
CMakeChache.txt
CMakeFiles/
bin/

cmake 使った事ない人は逆にMakeFileがgitにトラッキングされないのに最初不安に覚えるかもしれません。(自分もそうでした)
が、慣れてくると「あ、もうCMakeListだけでいいや…」って感覚になってきます

3.extlibにgit submoduleでiglooの安定版をリポジトリに登録させる

安定版のタグ情報はここから追ってください

$ cd ~/repo/project_root/
$ cd ./extlib/
$ git submodule add https://github.com/joakimkarlsson/igloo.git
$ pushd ./igloo/
$ git checkout igloo.1.1.1
$ cd ../../
$ git add extlib/igloo/
$ git commit -m "[Added] (submodule) Igloo BDD framework"

4.CMakeList.txtにiglooとテストディレクトリへのパスを作る

とりあえず最初はBDDできる最小限の設定だけ書きます

CMakeList.txt
# お持ちのcmakeのバージョンを指定
cmake_minimum_required(VERSION 2.8)

# executable なターゲットの投げ先を指定できます
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ./bin)

# 普通にプロジェクト名入れてね
project(wonderfull_very_very_nice_project)

# 最初からバージョン番号入れておいた方がいいです
set(serial "0.1.0")

# インクルードするヘッダがある場所をcmakeに教えてあげる
include_directories("${PROJECT_SOURCE_DIR}" extlib/igloo include)

# テストを実行する上で依存関係のあるファイル群をこうやって指定できます
file(GLOB TestSourceFiles t/*.cpp src/*.c)

# 実行ターゲットの追加
add_executable(run_test ${TestSourceFiles})

# ターゲットに対し、ビルド後に任意のコマンドを実行させられます
# 以下の例はテストのソースをコンパイル後にテスト実行まで自動化するようにしたもの
add_custom_command(
    TARGET run_test
    POST_BUILD
    COMMAND run_test
    WORKING_DIRECTORY ./bin)

5.テストランナーのmain.cppを置く

テストコードのディレクトリのトップにmain.cppを置きます。

main.cpp
#include <igloo/igloo.h>
using namespace igloo;

int
main(int argc, const char* argv[])
{
    return TestRunner::RunAllTests(argc, argv);
}

これで開発体制が整いました。
この時点でgit commitしてmasterブランチからgit flowなりgithub-flowなりをする起点にするとよいかと思います

6.あとは普通のBDDサイクルを繰り返す

6-a.まずテスト書く

=> テストの書き方は本家を参考にしてください
一応軽く、C言語で書いたライブラリのテストの場合だとこう書くってサンプル書いておきます。

なお、Context - Spec な、ストーリーベースなBDDの場合は <igloo/igloo.h> を、
Describe - It / When - Then な、オブジェクトベースなBDDの場合は <igloo/igloo_alt.h>
インクルードします。

linked_list.cpp
#include <igloo/igloo_alt.h>
using namespace igloo;
/* ここまでテンプレ */

#include "my_linked_list.h" /* テスト対象のAPIが宣言されてるヘッダ */

Describe(MyLinkedList)
{
    linked_list_t *list;

    void SetUp()
    {
        list = new_linked_list();
    }

    void TearDown()
    {
        free_linked_list(list);
    }

    It(Has_no_member_when_initialized)
    {
        Assert::That(is_list_empty(list), IsTrue());
    }
}; // Describe(){}; はセミコロン必須

6-b.cmake CMakeList.txtしてビルド設定を更新

  • 新規にテストコードが書かれたファイルを追加した時
  • 新規にヘッダファイルを追加した時
  • 新規に実装コードが書かれたファイルを追加した時
  • ビルド設定を変更した時

以上のどれかの場合は以下のコマンドでMakefileを更新します

$ cmake CMakeList.txt

これで、cmakeが新しいファイルをビルド設定に追加してくれます。便利ですね

いちいち CMakeList.txt を指定する必要もなく、

$ cmake .

これで十分です

6-c.makeでテスト走らせる

$ make
Scanning dependencies of target run_test
[ 50%] Building CXX object CMakeFiles/run_test.dir/t/linked_list.cpp.o
.../.../.../t/linked_list.cpp:XX:XX error: ...

当然最初はコンパイルエラーで終わります。

最初はそもそも

  • ”hoge.hってincludeしてるけどそんなファイルねぇよ!バカ!”
  • "hogehoge(foo);って呼んでるけどそんなAPIねーじゃねーか!バカ!"
  • "ヘッダには宣言されてるけど実装どこにもねーじゃねーか!バカ!"
  • "…!…!…もうバカ!”

っていっぱい怒られますけど、負けないでください

6-d.実装して 6-b -> 6-c の繰り返し

普通にヘッダ書いて、実装して

$ cmake .
$ make clean
$ make

の繰り返しです。ファイルを追加してない、ビルド設定を変えてない、などであれば make コマンドだけで良いです。
好みのスクリプト言語でエディタにフックかけておくと捗ると感じる人もいるかもしれません。
(自分はよくtypoしたり、細かい変数のリネームが多いのでエディタにフックはしない派ですが)

Reference - 参考リンク