CppUMockでハード依存のテストも行う

本ページでは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++で記述方法が変わるので注意してください。

スクロールできます
テストコードモック関数
boolwithBoolParameters(const SimpleString& name, bool value) [C]
withBoolParameter(const SimpleString& name, bool value) [C++]
intwithIntParameters(const SimpleString& name, int value) [C]
withIntParameter(const SimpleString& name, int value) [C++]
unsigned intwithUnsignedIntParameters(const SimpleString& name,
                          unsigned int value) [C]

withUnsignedIntParameter(const SimpleString& name,
                         unsigned int value) [C++]
longwithLongIntParameters(const SimpleString& name, long value) [C]
withLongIntParameter(const SimpleString& name, long value) [C++]
unsigned longwithUnsignedLongIntParameters(const SimpleString& name,
                              unsigned long value) [C]

withUnsignedLongIntParameter(const SimpleString& name,
                             unsigned long value) [C++]
long longwithLongLongIntParameters(const SimpleString&,
                          cpputest_longlong value) [C]

withLongLongIntParameter(const SimpleString&,
                         cpputest_longlong value) [C++]
unsigned long longwithUnsignedLongLongIntParameters(const SimpleString& name,
                                  cpputest_ulonglong value) [C]

withUnsignedLongLongIntParameter(const SimpleString& name,
                                 cpputest_ulonglong value) [C++]
doublewithDoubleParameters(const SimpleString& name, double value) [C]
withDoubleParameter(const SimpleString& name, double value) [C++]
charwithStringParameters(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にて型を比較する方法などを渡す必要があります。
@2installComparatorでCppUMockに渡す関数です。何が正解かわかりませんが、このとおり記述しておけばとりあえず動きます。
@3Comparatorの情報を削除します。最後にこれがないと正常に動きません。
解説

戻り値

戻り値の宣言は、型に合わせて次のものがあります。CとC++でモック関数内の記述方法が変わるので注意してください。

スクロールできます
テストコードモック関数
boolandReturnBoolValueboolReturnValue [C]
returnBoolValue [C++]
intandReturnIntValueintReturnValue [C]
returnIntValue [C++]
unsigned intandReturnUnsignedIntValueunsignedIntReturnValue [C]
returnUnsignedIntValue [C++]
longandReturnLongIntValuelongIntReturnValue [C]
returnLongIntValue [C++]
unsigned longandReturnUnsignedLongIntValueunsignedLongIntReturnValue [C]
returnUnsignedLongIntValue [C++]
long longandReturnLongLongIntValuelongLongIntReturnValue [C]
returnLongLongIntValue [C++]
unsigned long longandReturnUnsignedLongLongIntValuensignedLongLongIntReturnValue [C]
returnUnsignedLongLongIntValue [C++]
doubleandReturnDoubleValuedoubleReturnValue [C]
returnDoubleValue [C++]
charandReturnStringValuestringReturnValue [C]
returnStringValue [C++]
ポインターandReturnPointerValuepointerReturnValue [C]
returnPointerValue [C++]
ポインターandReturnConstPointerValueconstPointerReturnValue [C]
returnConstPointerValue [C++]
関数ポインターandReturnFunctionPointerValuefunctionPointerReturnValue [C]
returnFunctionPointerValue [C++]
戻り値リスト

コメント

コメントする

CAPTCHA


目次