rydotの呟''

プログラミングとかCGとかDTMとか適当にいろいろのことを適度にやる気なく綴るはず。

C言語のライブラリをC++からテストするようにしてみた

C言語で書かれているライブラリがあって、バグがポコポコ出てくるのでなんとかしてテストを仕込みたいと思った。その顛末を書く。
このCのライブラリはそのままC++でも通るコードなのであるが、バージョン管理がVSSであるためファイル名を変えたりすると履歴が吹っ飛ぶ。*1わりと仕方なくCで運用されている。
手動で後片付けとか書きたくないしマクロの嵐とかこわいしgoogletestとか便利なのでC++からこれを呼び出すことは決めていた。

非static関数をテストできるようにした

ライブラリは非static関数とstatic関数からなる。翻訳単位内でしか使わない関数はだいたいstaticにしてある。
外部からアクセスされる非static関数のテストはまあ簡単にできた。C++側ではマングリングを回避するためにextern "C"をつける必要がある。

// hoge.c
int fuga( int x );
static int static_hoge( int x ) {
  return fuga( x );
}
int hoge( int x ) {
  return static_hoge( x );
}
// fuga.c
int fuga( int x ) {
  return x;
}
// hoge_test.cpp
#include <gtest/gtest.hpp>
extern "C" int hoge( int x ); // マングリングを回避するためのextern "C"
TEST( hoge, hoge ) {
  EXPECT_EQ( hoge(1), 1 );
}

libhogeをリンクする必要がある。*2
十分に小さい規模の関数群なら非static関数だけテストしておけば事足りるかもしれない。
しかしあんまりテストされていないstatic関数がたくさんある場合は心許ない。

static関数をテストできるようにした

通常はあんまりやらない方法をテストの時だけ行うということはよくある。
includeは通常はヘッダファイルを貼付けるために使うが、Cファイルを直接貼付けることも普通にできる。

// hoge_test.cpp
extern "C" {
#include "hoge.c"
}
#include <gtest/gtest.hpp>
TEST( static_hoge, hoge ) {
  EXPECT_EQ( static_hoge(1), 1 );
}

だいたいコンパイルは通ると思う。必要に応じて調整を行う。*3
さて、コンパイルは通るけれどもリンクが通らない。
libhogeをリンクしてしまうとhogeが多重定義でエラーとなる。一方、リンクしなければfugaが未定義でエラーとなる。
そこでライブラリ側のすべてのCファイルを、対応するC++ファイルに上記の要領でincludeし、libhogeなしでリンクする。

// fuga_test.cpp
extern "C" {
#include "fuga.c"
}
// another_test.cpp
extern "C" {
#include "another.c"
}

...

ファイルが10個を超えると脳みそ腐るのでスクリプトか何かで自動化したほうがよい。*4
そんなこんなでだいぶテストが書きやすくなった気がする。

*1:当然のことながらローカルでは自分用Gitで管理している。めんどくさいけどVSS単体よりかはだいぶまし。

*2:通常の使い方なので通常通り。

*3:不思議なdefineがC内で行われている場合はそっとundefするなど。

*4:実際にやる気なくなったので実証済み。とっととスクリプト書けよという。