センサーを読むCの関数は、実機がないとテストしにくいことがあります。CMockを使うと、センサーの読み取りをモックに置き換え、好きな値で判定処理をテストできます。
CMockの役割
CMockは、Cのヘッダからモックを生成するツールです。依存関数の戻り値を指定し、呼び出し回数や引数を検証できます。公式の説明も参照してください。
今回は、テストの判定にUnity、モック生成にCMock、生成からビルド・実行までの管理にCeedlingを使います。UnityとCMockはCeedlingに同梱されます。
準備
Ruby 3とGCCを用意し、ruby・gem・gccをターミナルから実行できる状態にしてください。導入方法はCeedlingの公式手順にあります。
gem install ceedling -v 1.0.1 --no-document
mkdir cmock-sample
cd cmock-sample
mkdir src test
Windowsでは、プロジェクトを英数字だけのパスに作成してください。以下の5ファイルを作れば、添付ファイルなしで試せます。
この記事ではCeedling 1.0.1を指定しています。実行時の_1.0.1_も、使用するバージョンの指定です。
cmock-sample/
├── project.yml
├── src/
│ ├── sensor.h
│ ├── alarm.h
│ └── alarm.c
└── test/
└── test_alarm.c
コードと設定
src/sensor.hは、依存関数の宣言です。今回はモックを使うため、sensor.cは不要です。
#ifndef SENSOR_H
#define SENSOR_H
int sensor_read(int channel);
#endif
src/alarm.hに、テスト対象の関数を宣言します。
#ifndef ALARM_H
#define ALARM_H
int alarm_is_hot(void);
#endif
src/alarm.cは、チャネル1の温度が30以上なら1、それ未満なら0を返します。
#include "alarm.h"
#include "sensor.h"
int alarm_is_hot(void)
{
return sensor_read(1) >= 30;
}
test/test_alarm.cで、境界の前後を確認します。
#include "unity.h"
#include "alarm.h"
#include "mock_sensor.h"
void setUp(void) {}
void tearDown(void) {}
void test_below_threshold(void)
{
sensor_read_ExpectAndReturn(1, 29);
TEST_ASSERT_EQUAL_INT(0, alarm_is_hot());
}
void test_at_threshold(void)
{
sensor_read_ExpectAndReturn(1, 30);
TEST_ASSERT_EQUAL_INT(1, alarm_is_hot());
}
void test_above_threshold(void)
{
sensor_read_ExpectAndReturn(1, 31);
TEST_ASSERT_EQUAL_INT(1, alarm_is_hot());
}
sensor_read_ExpectAndReturn(1, 29)は、「引数1で1回呼ばれ、そのとき29を返す」という設定です。引数や呼び出し回数が合わなければ、テストは失敗します。
mock_sensor.hは自分で書きません。テストでこのヘッダをインクルードすると、CeedlingがCMockを使ってsensor.hからモックを自動生成します。
最後に、プロジェクト直下へproject.ymlを作成します。
---
:project:
:build_root: build
:use_mocks: TRUE
:paths:
:test:
- test/**
:source:
- src/**
:include:
- src/**
:cmock:
:mock_prefix: mock_
:plugins:
:enabled:
- report_tests_pretty_stdout
実行
project.ymlがあるディレクトリで実行します。
ceedling _1.0.1_ test:all
Ceedlingがモック生成、コンパイル、リンク、テスト実行を行います。アプリケーション用のmain()も不要です。
Windows環境のRuby 3.4.11、GCC 14.2.0、Ceedling 1.0.1、CMock 2.6.0、Unity 2.6.1で実行し、3件すべて成功しました。出力の要点は次のとおりです。
OVERALL TEST SUMMARY
TESTED: 3
PASSED: 3
FAILED: 0
IGNORED: 0
失敗を検出できることも確認できます。alarm.cの>= 30を> 30に変えて再実行すると、温度30のテストがExpected 1 Was 0で失敗します。確認後は>= 30に戻してください。
このテストで確認するのは、指定したセンサー値に対する判定と、依存関数の呼び出しです。実際のセンサーや通信処理の動作は、別途確認します。