#include Test Double
出典:
http://www.renaissancesoftware.net/blog/archives/231
Copyright © James Grenning
著者のJames Grenning氏の許諾を得たので日本語訳を公開します。
今日はレガシーなCコードにテストを追加する1日目だ。しかし、テストハーネスに入れようとしているコードが開発マシン上ではコンパイルできずに、にっちもさっちも行かなくなってしまった。クラッシュさせて成功させる(Crash to Pass)ための「コンパイルを成功させる」ステップで行き詰ってしまったということだ。
レガシーな組み込みCコード(テストのない組み込みCコード)をテストハーネスに入れるのは骨の折れる作業になる。レガシーCコードは、ターゲットのプロセッサに強く結びついていることが多い。プロダクトコードでは問題にならないかもしれないが、オフターゲットのユニットテストでは大きな問題となる。
C言語には、依存関係を断ち切るためのメカニズムがあまりない。私の本では、関数ポインタによる置き換えについて多くのページを割いて説明したが、プリプロセッサによるスタブ化についてはほんの触り程度だった。
この記事では、問題のある#includeファイルの依存関係を断ち切る方法として、#include Test-Doubleについて見ていこう。
マイコンベンダーから提供された、ターゲット依存のヘッダーファイルがあるとしよう。これは私が先週出くわしたのと同じようなものだ:
/* * ---- acmetypes.h ---- */ #ifndef _ACME_STD_TYPES #define _ACME_STD_TYPES #if defined(_ACME_X42) typedef unsigned int Uint_32; typedef unsigned short Uint_16; typedef unsigned char Uint_8; typedef int Int_32; typedef short Int_16; typedef char Int_8; #elif defined(_ACME_A12) typedef unsigned long Uint_32; typedef unsigned int Uint_16; typedef unsigned char Uint_8; typedef long Int_32; typedef int Int_16; typedef char Int_8; #else #error <acmetypes.h> is not supported for this environment #endif #endif /* _ACME_STD_TYPES */
Cのプリプロセッサは#errorに到達した時点で停止してしまう。単に_ACME_X42か_ACME_A12をdefineすればよいと思うかもしれない。しかし、オフターゲットでのテストを行っている場合、とくに(私がこの文章を書くのに使っているのと同じように)64ビットマシンを使っている場合はint型のサイズが問題となる。
どんなときでもプロダクトコードのヘッダーファイルをインクルードできれば一番だが、上手くいかないときもある。とりわけレガシーコードを相手にしているときはそうだ。だが、あきらめる必要はない。そういう時は、#include Test-Doubleを使おう。
今回のケースはわりと簡単だ。同じ名前で新たなヘッダーファイルを作成し、プロダクトコードのヘッダーファイルよりも先に検索されるインクルードパス上に保存しよう。#include Test-Doubleは以下のようになる:
/* * acmetypes.h - test double for off target testing */ #ifndef ACMETYPES_H_ #define ACMETYPES_H_ #include <stdint.h> typedef uint32_t Uint_32; typedef uint16_t Uint_16; typedef uint8_t Uint_8; typedef int32_t Int_32; typedef int16_t Int_16; typedef int8_t Int_8; #endif /* ACMETYPES_H_ */
オフターゲットのホスト開発システム環境ではネイティブ実装のstdint.hがあるだろう。それが固有のintサイズに対処しなければならない場合の一般的な方法だ。上の例では、単純にAcmeの型を可搬性のある型で再定義している。
CppUTestとmakefileを使っているなら、以下のようにしてプロダクトコードのヘッダーファイルより先にテストダブルのヘッダーファイルを探しに行くようにmakefileに指示する事ができる:
INCLUDE_DIRS =\ .\ include \ include/* \ $(CPPUTEST_HOME)/include/ \ mocks/includes \ $(ACME_INCLUDES) \
$(ACME_INCLUDES)はマイコンベンダー依存のインクルードファイルが置かれたディレクトリを指す。テストダブル用のヘッダーファイルをmocks/includesなどのフォルダに置こう。INCLUDE_DIRS上でmocks/includesが$(ACME_INCLUDES)より前にあれば、テストダブル用のヘッダーファイルでプロダクトコードのヘッダーファイルを置き換えることができる。プロダクトコードをビルドするときは、mocks/includesはインクルードパスには含まれない。
CppUTestのmakefileを使わなくても同じことができる。私が知っている全てのコンパイラは、インクルードパスをサポートしている。
全てが上手く行っている事を確認するために、次のようなテストケースを書くことができる。
extern "C" { #include "acmetypes.h" } #include "CppUTest/TestHarness.h" TEST_GROUP(acmetypes) {}; TEST(acmetypes, checkIntSizes) { LONGS_EQUAL(1, sizeof(Uint_8)); LONGS_EQUAL(1, sizeof(Int_8)); LONGS_EQUAL(2, sizeof(Uint_16)); LONGS_EQUAL(2, sizeof(Int_16)); LONGS_EQUAL(4, sizeof(Uint_32)); LONGS_EQUAL(4, sizeof(Int_32)); }
ほかにも#includeスタブを使える状況がある。例えば、数個のシンボルだけが必要なときに、長いインクルードのチェインを断ち切りたい場合だ。だが、プロダクトコードのヘッダーが使えるならそれが一番だということは心に留めておいて欲しい。
この記事が役にたったなら教えて欲しい。もし必要な情報が足りない場合も教えて欲しい。