オルトプラスエンジニアの日常をお伝えします!

テーブル駆動テスト in C++

この記事はAltplus Advent Calendar 2017の13日目のエントリです。

最近なぜかJava関連のアカウントにTwitterでフォローされることが多い、JavaマスコットのDukeを愛してやまない竹田です。

テーブル駆動テスト

今回は、Go wikiで紹介されて、Goコミュニティでよく使われている(?)テーブル駆動開発をC++でやってみたいと思います。

テーブル駆動テストとは、テストの入力と期待値をテーブルの行に記載し、テーブルを走査しながら実行していくテストのことです。

例えば、int型の入力を受け取って、2倍する関数int twice(int x)のテスト・テーブルは以下のようになります。

in(入力) out(出力)
0 0
1 2
2 4

このテーブルを使って、テーブル駆動テストを書くと以下のようになります。

#include <cassert>

int twice(int x){
    return x * 2;
}

int main()
{
   struct { int in; int out; } table[]{
       {0, 0},
       {1, 2},
       {2, 4}
   };
    
   for(auto [in, out]: table){
    assert(twice(in) == out);
   }
}

テスト・ケース毎にAssertを書く必要がなくなり、テーブルに入出力がまとめられているため、関数の入出力仕様に集中することができます。入出力で仕様が決定される純粋な関数のテストに役立ちます。

関数の引数が増えてもテーブルの列を追加するだけで対応できます。文字列とインデックスを取って、インデックス番目の文字を返すat関数のテーブルは、

文字列(in1) インデックス(in2) 文字(out)
"abc" 0 'a'
"Hello" 3 'l'
"Hello world!" 7 'r'
#include <cassert>
#include <string_view>

char at(std::string_view s, int index){
    return s[index];
}

int main()
{
   struct { char const* str; int index; char out; } table[]{
       {"abc", 0, 'a'},
       {"Hello", 3, 'l'},
       {"Hello world!", 7, 'o' }
   };
    
   for(auto [s, i, out]: table){
    assert(at(s, i) == out);
   }
}

最後に

テーブル駆動テストを利用すると、関数の入出力仕様にフォーカスしたテスト・ケースを見通しよく記載することができます。

C++の"unnamed struct"(テーブルの定義に利用している名前なしのstruct)、"range-based for"、"structured bindings"を利用して、快適なテーブル駆動テストにしましょう!