本ページではCppUTestの機能の一部である、CppUMockの解説を行います。CppUTestがわからない方は、まずは下記ページからどうぞ。
はじめに
入力に対して計算を行い、出力を返すような関数であればテストは簡単です。しかしハードに依存する部分のテストは容易ではありません。たとえばマイコンのGPIO制御です。次のようなスイッチ入力を判定する処理があるとします。
/** ----------------------------------------------------------------------------
* @brief キーの確認
* @retval 0 : オフ
* @retval 1 : オン
*/
uint8_t Check_Key(void)
{
uint8_t key;
key = GPIOA.IDR & KEY1;
if (key == 0) {
return 1;
} else {
return 0;
}
}
上記例ではGPIOの制御がハード依存のため、このままではテストできません。そこでまずは、ハード依存の部分を関数化して隠します。HAL(Hardware Abstraction Layer:ハードウェア抽象化レイヤー)が用意されているならそれを使ってください。なければ似たような関数を自作して置き換えます。
key = GPIO_ReadPin(GPIOA, KEY1);
これで準備は完了です。
CppUMockの使用例
CppUTestには、CppUMockというモック(模造などの意味)機能があります。このモック機能を用いることで、本来の関数を模造したダミー関数を作ることができます。今回の例でいうとGPIO_ReadPinのモック関数を用意し、テストするのに都合の良い応答を返すようにします。
- CppUMockはCとC++で記述方法が少し変わります。以降の解説はCをベースに行いますが、あとでCとC++の比較も掲載しています。
さて、さっそくですがGPIO_ReadPinのモック関数が次のようになったとします。
uint8_t GPIO_ReadPin(uint16_t port, uint8_t pin)
{
mock_c()
->actualCall("GPIO_ReadPin")
->withUnsignedIntParameters("port", port)
->withUnsignedIntParameters("pin", pin);
return mock_c()->intReturnValue();
}
actualCall("GPIO_ReadPin") | GPIO_ReadPinという関数です。 |
withUnsignedIntParameters("port", port) | 1つ目の引数はunsigned型で、その名前は「port」です。引数の宣言は型によって変わります。 |
withUnsignedIntParameters("pin", pin) | 2つ目の引数はunsigned int型で、その名前は「pin」です。 |
intReturnValue() | 戻り値はint型です。戻り値の宣言は型によって変わります。 |
このモック関数に期待した動きをさせるには、次のように記述します。
mock_c()
->expectOneCall("GPIO_ReadPin")
->withUnsignedIntParameters("port", GPIOA)
->withUnsignedIntParameters("pin", KEY1)
->andReturnIntValue(0);
expectOneCall("GPIO_ReadPin") | GPIO_ReadPin関数が1度呼び出されたときの期待する効果を記録します。 |
withUnsignedIntParameters("port", GPIOA) | 1つ目の引数「port」はunsigned int型で、GPIOAを渡します。引数の宣言は型によって変わります。 |
withUnsignedIntParameters("pin", KEY1) | 2つ目の引数「pin」はunsigned int型で、KEY1を渡します。 |
andReturnIntValue(0) | 関数の戻り値として「0」を返します。 |
たとえばチャタリング対策で3回連続で同じ値なら採用する、というテストを行う場合は、3回記述します。
mock_c()
->expectOneCall("GPIO_ReadPin")
->withUnsignedIntParameters("port", GPIOA)
->withUnsignedIntParameters("pin", KEY1)
->andReturnIntValue(0);
mock_c()
->expectOneCall("GPIO_ReadPin")
->withUnsignedIntParameters("port", GPIOA)
->withUnsignedIntParameters("pin", KEY1)
->andReturnIntValue(0);
mock_c()
->expectOneCall("GPIO_ReadPin")
->withUnsignedIntParameters("port", GPIOA)
->withUnsignedIntParameters("pin", KEY1)
->andReturnIntValue(0);
または「expectNCalls」を用いることで、指定回数分の挙動を一度に設定することもできます。
mock_c()
->expectNCalls(3, "GPIO_ReadPin") // 期待した効果を3回分記録する
->withUnsignedIntParameters("port", GPIOA)
->withUnsignedIntParameters("pin", KEY1)
->andReturnIntValue(0);
サンプルコード
先のテストコードの全体を示します。
テスト対象コード
key.c:テスト対象
//------------------------------------------------------------------------------
// include
//------------------------------------------------------------------------------
#include "gpio.h"
#include "key.h"
/** ----------------------------------------------------------------------------
* @brief キーの確認
* @retval 0 : オフ
* @retval 1 : オン
*/
uint8_t Check_Key(void)
{
uint8_t key;
key = GPIO_ReadPin(GPIOA, KEY1);
if (key == 0) {
return 1;
} else {
return 0;
}
}
key.h:テスト対象
#ifndef KEYH
#define KEYH
#ifdef __cplusplus
extern "C" {
#endif
//------------------------------------------------------------------------------
// include
//------------------------------------------------------------------------------
#include <stdint.h>
//------------------------------------------------------------------------------
// function
//------------------------------------------------------------------------------
uint8_t Check_Key(void);
#ifdef __cplusplus
}
#endif
#endif
gpio.h:GPIOのHALサンプル
#ifndef GPIOH
#define GPIOH
#ifdef __cplusplus
extern "C" {
#endif
//------------------------------------------------------------------------------
// include
//------------------------------------------------------------------------------
#include <stdint.h>
//------------------------------------------------------------------------------
// define
//------------------------------------------------------------------------------
#define KEY1 (0x01)
//------------------------------------------------------------------------------
// function
//------------------------------------------------------------------------------
uint8_t GPIO_ReadPin(uint16_t port, uint8_t pin);
#ifdef __cplusplus
}
#endif
#endif
Cのテストコード
keyTest.cpp:モックを用いたCのテストコード
#include "CppUTest/TestHarness.h"
#include "CppUTestExt/MockSupport_c.h"
#include "gpio.h"
#include "key.h"
// clang-format off
TEST_GROUP(key){
TEST_SETUP(){
}
TEST_TEARDOWN(){
mock_c()->clear(); // モック資源のクリアー
}
};
// clang-format on
TEST(key, Test_Mock)
{
uint8_t key;
mock_c()
->expectOneCall("GPIO_ReadPin")
->withUnsignedIntParameters("port", GPIOA)
->withUnsignedIntParameters("pin", KEY1)
->andReturnIntValue(0);
key = Check_Key();
LONGS_EQUAL(key, 1);
// 結果検証
mock_c()->checkExpectations();
}
/** ----------------------------------------------------------------------------
* @brief モック関数
*/
uint8_t GPIO_ReadPin(uint16_t port, uint8_t pin)
{
mock_c()
->actualCall("GPIO_ReadPin")
->withUnsignedIntParameters("port", port)
->withUnsignedIntParameters("pin", pin);
return mock_c()->intReturnValue();
}
C++のテストコード
keyTest.cpp:モックを用いたC++のテストコード
#include "CppUTest/TestHarness.h"
#include "CppUTestExt/MockSupport.h"
#include "gpio.h"
#include "key.h"
TEST_GROUP(key){
TEST_SETUP(){
}
TEST_TEARDOWN(){
mock().clear();
}
};
TEST(key, Test_Mock)
{
uint8_t key;
mock()
.expectOneCall("GPIO_ReadPin")
.withUnsignedIntParameters("port", port)
.withUnsignedIntParameters("pin", pin)
.andReturnIntValue(0);
key = Check_Key();
LONGS_EQUAL(key, 1);
// 結果検証
mock().checkExpectations();
}
/** ----------------------------------------------------------------------------
* @brief モック関数
*/
uint8_t GPIO_ReadPin(uint16_t port, uint8_t pin)
{
mock()
.actualCall("GPIO_ReadPin")
.withUnsignedIntParameter("port", port)
.withUnsignedIntParameter("pin", pin);
return mock().returnIntValue();
}
引数
引数の宣言は、型に合わせて次のものがあります。一部を除いてCとC++で記述方法が変わるので注意してください。
型 | テストコード | モック関数 |
---|---|---|
bool | withBoolParameters(const SimpleString& name, bool value) [C] withBoolParameter(const SimpleString& name, bool value) [C++] | |
int | withIntParameters(const SimpleString& name, int value) [C] withIntParameter(const SimpleString& name, int value) [C++] | |
unsigned int | withUnsignedIntParameters(const SimpleString& name, unsigned int value) [C] withUnsignedIntParameter(const SimpleString& name, unsigned int value) [C++] | |
long | withLongIntParameters(const SimpleString& name, long value) [C] withLongIntParameter(const SimpleString& name, long value) [C++] | |
unsigned long | withUnsignedLongIntParameters(const SimpleString& name, unsigned long value) [C] withUnsignedLongIntParameter(const SimpleString& name, unsigned long value) [C++] | |
long long | withLongLongIntParameters(const SimpleString&, cpputest_longlong value) [C] withLongLongIntParameter(const SimpleString&, cpputest_longlong value) [C++] | |
unsigned long long | withUnsignedLongLongIntParameters(const SimpleString& name, cpputest_ulonglong value) [C] withUnsignedLongLongIntParameter(const SimpleString& name, cpputest_ulonglong value) [C++] | |
double | withDoubleParameters(const SimpleString& name, double value) [C] withDoubleParameter(const SimpleString& name, double value) [C++] | |
char | withStringParameters(const SimpleString& name, const char* value) [C] withStringParameter(const SimpleString& name, const char* value) [C++] | |
ポインター | withPointerParameters(const SimpleString& name, void* value) [C] withPointerParameter(const SimpleString& name, void* value) [C++] | |
ポインター | withConstPointerParameters(const SimpleString& name, const void* value) [C] withConstPointerParameter(const SimpleString& name, const void* value) [C++] | |
(値を返す)ポインター | withOutputParameter(const SimpleString& name, void* output) [C/C++] | |
関数ポインター | withFunctionPointerParameters(const SimpleString& name, void (*value)()) [C] withFunctionPointerParameter(const SimpleString& name, void (*value)()) [C++] |
独自の型
引数の型には上の表以外に独自に定義した型を使用できます。この場合の記述方法が少し複雑なので、下に例を示します。なお下記例の記述方法が本当に正しいのかは、インターネットで検索しても例が殆どなく、正直よくわかっていません。またCppUMockはCでしか使用したことがなく、C++での記述例は用意できていません。
型 | テストコード | モック関数 |
---|---|---|
独自の型 | withParameterOfType(const SimpleString& type, const SimpleString& name, const void* value) [C/C++] | |
独自の型 | withOutputParameterOfType(const SimpleString& type, const SimpleString& name, void* output) [C/C++] |
keyTest.cpp:モックを用いたCのテストコード
#include "CppUTest/TestHarness.h"
#include "CppUTestExt/MockSupport_c.h"
#include "gpio.h"
#include "key.h"
static int equalMethod(const void *object1, const void *object2); // @2
static const char *toStringMethod(const void *); // @2
// clang-format off
TEST_GROUP(key){
TEST_SETUP(){
}
TEST_TEARDOWN(){
mock_c()->clear(); // モック資源のクリアー
}
};
// clang-format on
TEST(key, Test_Mock)
{
uint8_t key;
mock_c()->installComparator("GPIO_TypeDef", equalMethod, toStringMethod); // @1
mock_c()
->expectOneCall("GPIO_ReadPin")
->withParameterOfType("GPIO_TypeDef", "port", &GPIOA)
->withUnsignedIntParameters("pin", KEY1)
->andReturnIntValue(0);
key = Check_Key();
LONGS_EQUAL(key, 1);
mock_c()->removeAllComparatorsAndCopiers(); // @3
// 結果検証
mock_c()->checkExpectations();
}
/** ----------------------------------------------------------------------------
* @brief モック関数
*/
uint8_t GPIO_ReadPin(GPIO_TypeDef *port, uint8_t pin)
{
mock_c()
->actualCall("GPIO_ReadPin")
->withParameterOfType("GPIO_TypeDef", "port", port)
->withUnsignedIntParameters("pin", pin);
return mock_c()->intReturnValue();
}
// @2
static int equalMethod(const void *object1, const void *object2)
{
return object1 == object2;
}
// @2
static const char *toStringMethod(const void *)
{
return "string";
}
番号 | 解説 |
---|---|
@1 | 独自の型を用いる場合、installComparatorにて型を比較する方法などを渡す必要があります。 |
@2 | installComparatorでCppUMockに渡す関数です。何が正解かわかりませんが、このとおり記述しておけばとりあえず動きます。 |
@3 | Comparatorの情報を削除します。最後にこれがないと正常に動きません。 |
戻り値
戻り値の宣言は、型に合わせて次のものがあります。CとC++でモック関数内の記述方法が変わるので注意してください。
型 | テストコード | モック関数 |
---|---|---|
bool | andReturnBoolValue | boolReturnValue [C] returnBoolValue [C++] |
int | andReturnIntValue | intReturnValue [C] returnIntValue [C++] |
unsigned int | andReturnUnsignedIntValue | unsignedIntReturnValue [C] returnUnsignedIntValue [C++] |
long | andReturnLongIntValue | longIntReturnValue [C] returnLongIntValue [C++] |
unsigned long | andReturnUnsignedLongIntValue | unsignedLongIntReturnValue [C] returnUnsignedLongIntValue [C++] |
long long | andReturnLongLongIntValue | longLongIntReturnValue [C] returnLongLongIntValue [C++] |
unsigned long long | andReturnUnsignedLongLongIntValue | nsignedLongLongIntReturnValue [C] returnUnsignedLongLongIntValue [C++] |
double | andReturnDoubleValue | doubleReturnValue [C] returnDoubleValue [C++] |
char | andReturnStringValue | stringReturnValue [C] returnStringValue [C++] |
ポインター | andReturnPointerValue | pointerReturnValue [C] returnPointerValue [C++] |
ポインター | andReturnConstPointerValue | constPointerReturnValue [C] returnConstPointerValue [C++] |
関数ポインター | andReturnFunctionPointerValue | functionPointerReturnValue [C] returnFunctionPointerValue [C++] |
コメント