11
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

テスト駆動開発による組み込みプログラミング~モック&フラッシュドライバ編~

Posted at

今までの続き。

テスト駆動開発による組み込みプログラミングを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言語のテストにならないです。

本の残りも随時やっていきます。

11
14
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?