今までの続き。
テスト駆動開発による組み込みプログラミングをgoogletestでやる
テスト駆動開発による組み込みプログラミングをgoogletestでやる8章
前置き
Amazon.co.jp: テスト駆動開発による組み込みプログラミング ―C言語とオブジェクト指向で学ぶアジャイルな設計
繰り返しになりますが、組込みでテスト駆動開発を行うにあたってのバイブルです。10章ではFlashドライバを題材として、モックを扱います。
ただ、本に書いてある内容を動かそうとするだけで、それなりの難易度です。色々端折られていて、本の内容を写経しただけでは動きません!(サンプルとして完成したコードは配布されています)
そこで、テストを1個1個、順番に動かしたレポジトリを用意しました。
https://github.com/tomoyuki-nakabayashi/TDDforEmbeddedC_FlashDriver
コミット履歴を整理するのが面倒なので、最初からやってみたい方は、pass FirstWrite(SHA:cba030eac89b87e6a9d3ce33a0418e99db8c1a0b)から始めて下さい。
MockIOのコード
本の中で紹介されているMockは200行以上もあり、とっつきにくいです。
googlemockが非常に優秀なので、同じことが次の25行くらいでできます。
#ifndef TEST_MOCK_MOCK_IO_H_
#define TEST_MOCK_MOCK_IO_H_
#include "gmock/gmock.h"
#include "IO.h"
class MockIO {
public:
MOCK_METHOD1(IO_Read, ioData(ioAddress));
MOCK_METHOD2(IO_Write, void(ioAddress, ioData));
};
extern MockIO *mockIO;
extern "C" {
void IO_Write(ioAddress addr, ioData data)
{
return mockIO->IO_Write(addr, data);
}
ioData IO_Read(ioAddress addr)
{
return mockIO->IO_Read(addr);
}
}
#endif // TEST_MOCK_MOCK_IO_H_
ね、簡単でしょう?
テストコードからのモックの使い方
例えば、テスト対象コードのFlash_Write(address, data)を呼び出したときに、IO_Write(address, data)が1度呼び出されることをテストしたい場合は、次のようになります。
TEST_F(FlashDriverTest, WriteSucceeds_ReadyImmediately)
{
EXPECT_CALL(*mockIO, IO_Write(address, data)).Times(1);
int result = Flash_Write(address, data);
}
事前にmockIOのオブジェクトを用意する必要があります。
googlemockはオブジェクトを使いまわすと、前回のEXPECT_CALLを引き継いでしまって意図せぬ動作になります。テストケースごとに作り直すと楽です。
#include "mock/MockIO.h"
MockIO *mockIO;
class FlashDriverTest : public ::testing::Test
{
virtual void SetUp()
{
mockIO = new MockIO();
}
virtual void TearDown()
{
delete mockIO;
}
};
モックからの返り値を設定した場合は、次のようにアクションを設定します。
これでIO_Read()を呼び出したときに返り値として、ReadyBitを返してくれます。
TEST_F(FlashDriverTest, WriteSucceeds_ReadyImmediately)
{
EXPECT_CALL(*mockIO, IO_Read(StatusRegister)).WillOnce(Return(ReadyBit));
int result = Flash_Write(address, data);
}
余談
C言語の関数をモックする場合は、モックの扱いに注意が必要です。過去めちゃくちゃハマりました。C言語の関数をモックしていて困ったら連絡下さい。力になれるかもしれません。
モックをグローバル領域に置かないといけないため、複数のテスト用ソースコードからモックを共有したい場合は、どこかで1度だけ、モックのポインタを定義するようにして下さい。
マルチスレッドで動かすと動作がおかしくなりそうですが、今までのところ、問題になったことはないです。
.cファイルをnamespace内でincludeして、namespace内で固有のモックオブジェクトを作ることもできます。この方法を取ると、モックオブジェクトの共有が不要になりますが、対象の.cファイルがc++のコンパイラでコンパイルされてしまい、厳密なC言語のテストにならないです。
本の残りも随時やっていきます。