用語 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化する必要があります。
これをプロダクトコードを変更せずに行いたいです。
プロダクトコードとモックの切り替え
VisualStudio環境 = GoogleTest環境のため
MSVCの場合はmock_driver以下のコードをビルドするようにします。
DRIVER_FILES
をgtest/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
を作ることです。
#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の関数につなぐようにします。
#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);
で、テストケースごとのモックの実態を設定します。
#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();
}
結果
コード
> cd build
> cmake ..
後に、"build/freertos_project.sln"を起動する。
※VisualStudio, CMakeをインストールしている必要があります。
GoogleTest, Mockはレポジトリ内に含んでいます。
備考
ここまでやって気づきましたが
テスト駆動開発による組み込みプログラミング~モック&フラッシュドライバ編~
と、やっていることがよく似ています。初めから参考にさせてもらえばよかった。。
- グローバル変数は付かない
- 本番コードを変更せずに差し替えられる
が差分かな。