9
15

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 3 years have passed since last update.

既存のC言語ファイルを変更せずにGoogleMockを使う

Posted at

用語 1

  • TDD : テスト駆動開発(Test Driven Development)
  • CUT : テスト対象コード(Code Under Test)
  • テストダブル(Test Double)
    • テスト中に、関数、データ、モジュール、ライブラリに成りすますもの。CUTは自分がテストダブルにたいしても本物のコラボレータと同じようにやりとりをする。

概要

テスト駆動開発による組み込みプログラミング――C言語とオブジェクト指向で学ぶアジャイルな設計
で紹介されている、組み込みシステムによるテスト駆動開発の実践です。

TDDの構築として重要なのは、CUTに対する

  • テストドライバー
  • テストダブル(stub, spy, mock, fakeなどのこと)

をどう作るか?ですが、
実際の組み込みプロダクトでは、フルスクラッチでコードを書くことはないため

***- 既存コードが存在する

  • C言語で書かれている
    ***

の前提条件があります。
このため、プロダクトコードに対してTDD環境構築のために
既存コードを変更したり、新規コードを追加したくない
ことがあります。

こちらを

  • テストハーネス : GoogleTest
  • テストダブル : GoogleMock
    で構築していきます。

この記事では既存のCで書かれたプロダクトコードを変更せずにGoogleMockを使う方法の紹介になります。

手順

フォルダ構成

ProjectRoot
├─gtest
│  ├─gtest_main.cpp
│  ├─led_control_unittest.cpp #"Mockを使ったテストケース"
│  ├─include
│  │  ├─gmock
│  │  └─gtest
│  ├─lib
│  └─mock_driver
│      ├─mock-LedDriver.cpp #"user_app/driver/LedDriver.cのMock"
│      └─mock-LedDriver.h
└─user_app #"本番環境で使用するプロダクトコード"
    ├─include
    │  ├─LedDriver.h #"LedDriver.c, mock-LedDriver.cpp 共通のヘッダ"
    │  └─led_control.h
    ├─driver
    │  ├─LedDriver.c     #"テスト対象コードが使うドライバ(この例では、このファイルがHWを制御すると仮定する。つまりMock化が必要"
    └─src
        ├─main.c
        └─led_control.c  #"CUT(テスト対象コード)"

やりたいこと

テスト対象をled_controlとします。
この場合、LedDriverが依存コンポーネントのため、ここをMock化する必要があります。
これをプロダクトコードを変更せずに行いたいです。
image.png

プロダクトコードとモックの切り替え

VisualStudio環境 = GoogleTest環境のため
MSVCの場合はmock_driver以下のコードをビルドするようにします。
DRIVER_FILESをgtest/CMakeLists.txtで使います。

ProjectRoot/CMakeLists.txt
if(MSVC)
  message("Use Mock Driver")
  file(GLOB DRIVER_FILES ./gtest/mock_driver/*.cpp)
else()
  # 未実装だが本番環境のビルド時には本物のディレクトリを指定.
  message("Not Use Mock Driver")
  file(GLOB DRIVER_FILES ./user_app/driver/*.c)
endif(MSVC)

Mockの実装

今回のテストで使う LedDriver_Create, LedDriver_Destroy を実装します。
ポイントはテストの定義ファイルから、Mockを渡せるように SetMockLedDriver を作ることです。

mock-LedDriver.h
#pragma once
#include "gtest/gtest.h"
#include "gmock/gmock.h"

class MockLedDriver {
public:
    MOCK_METHOD1(LedDriver_Create, void(uint16_t* adrs));
    MOCK_METHOD0(LedDriver_Destroy, void(void));
};
void SetdMockLedDriver(MockLedDriver* p);

cpp側です。
プロダクトコードから呼ばれた際にMockの関数につなぐようにします。

mock-LedDriver.cpp
#include "mock-LedDriver.h"
// -----------------------
// アプリケーションコードはCなのでextern "C" をしないと未解決のシンボルとなる.
// -----------------------
extern "C" {
#include "LedDriver.h"
}

static MockLedDriver* pMock;
void SetdMockLedDriver(MockLedDriver* p) {
    pMock = p;
}

// -----------------------
// ここにMockしたいAPIを書く
//   - アプリケーションコードからアクセスされたらMockのAPIに繋げる.
// -----------------------
void LedDriver_Create(uint16_t* address) {
    pMock->LedDriver_Create(0);
}

void LedDriver_Destroy(void) {
    pMock->LedDriver_Destroy();
}

テストケースの実装

led_controlのInitializeLED, FinalizeLEDが1回ずつ呼ばれるようなテストケースを作っていきます。
SetdMockLedDriver(&mock);で、テストケースごとのモックの実態を設定します。

led_control_unitttest.cpp
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "mock_driver/mock-LedDriver.h"
extern "C"
{
#include "led_control.h"
}

class LedControlUnitTest : public testing::Test {
protected:
	void SetUp() override {
	}
	void TearDown() override {
	}
};

TEST_F(LedControlUnitTest, Initialize_Finalize_LED) {
	MockLedDriver mock;
	// 1回呼ばれることを期待(※呼ぶ前に記述する必要がある)
	EXPECT_CALL(mock, LedDriver_Create(testing::_))   // "_"は値は任意でOK.
		.Times(1);
	EXPECT_CALL(mock, LedDriver_Destroy())
		.Times(1);
	SetdMockLedDriver(&mock);
	InitializeLED();
	FinalizeLED();
}

結果

無事、passしました。
image.png

コード

> cd build
> cmake ..

後に、"build/freertos_project.sln"を起動する。
※VisualStudio, CMakeをインストールしている必要があります。
 GoogleTest, Mockはレポジトリ内に含んでいます。

備考

ここまでやって気づきましたが
テスト駆動開発による組み込みプログラミング~モック&フラッシュドライバ編~
と、やっていることがよく似ています。初めから参考にさせてもらえばよかった。。

  • グローバル変数は付かない
  • 本番コードを変更せずに差し替えられる

が差分かな。

  1. テスト駆動開発による組み込みプログラミング

9
15
0

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
9
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?