組込みCテストフレームワークUnityを導入してテストコード生産性をUp

回路設計・ファーム開発担当のす〜(@ksksue)です。

弊社のBLEモジュールnRF52(Cortex-M4F)のファームウェア開発環境にテストフレームワークUnity導入したところ カンタンな導入でテストコード生産性が飛躍的に上がりました。今回はそのUnityの紹介&解説です。

IoT開発においてハードウェアとソフトウェアを組合せたテストは品質を担保するために大事な工程ですが、 テスト項目が多くなってくると非常に煩雑になりがちです。Unity導入によってその煩雑さが少なくなりました。

オレオレテストコードをいじりまわす日々

f:id:photosynth-inc:20171201203239j:plain

Unity導入前、最初はカンタンなオレオレテスト環境で進めていたのですが、テスト項目数が増えるに従ってその環境自体を場当たり的に改善していくといった作業が発生しました。 正直なことを言うと、改善したいという欲求にあがらえず、必要以上の作業をしていた部分があったと思います。 それが進むと今度は過去のテストコードの再利用性が悪くなり、それをカバーする作業発生といった悪循環におちいりました。 テストコードを書いているのか、環境改善しているのかよくわからないというひどい状況でした。

Unityを導入することによって、前述したようなテスト環境自体に手を加える必要性がなくなり、本質的な作業に集中できるようになりました。 テストコードスタイルが統一されることでコードの保守性がダントツによくなります。 色々な邪念がなくなりテストコード生産性が上がったことが最大の導入メリットだと思います。

機能は少ないが充分な組込みCテストフレームワーク

f:id:photosynth-inc:20171201203031j:plain

Unityと検索するといろいろと別のフレームワークの結果が出てくるのですが、本記事で扱うUnityはC言語テストフレームワークのUnityです。

Unityは主に以下の機能を提供します

  • アサーション : 整数や配列などの期待値と実体値を比較しPASS/FAILを判定。FAILの場合そのアサートのファイル名と行数を出力
  • レポート:何件PASS/FAILあったかをレポート

例えばアサーションは以下のように期待値と実行値を指定します。

TEST_ASSERT_EQUAL_HEX8(10, func(i));

func(i)の結果が10でなかった場合は以下のようにファイル名、行数、関数名、期待値と実行値を出力します。

..\test\unity_test.c:232:test_drv_spi_write__should__Write:FAIL: Expected 10 Was 0

レポート結果はシンプルにいくつテストし、FAILが何回発生したかが出力されます。

-----------------------
10 Tests 0 Failures 0 Ignored 

他のテストフレームワークに比べると機能は少ないですが組込みCという環境を考えると充分と言えるでしょう。

さて、前置きが長くなりましたが以下導入方法の説明に入ります。

動作確認環境

  • MPU : nRF52832(Cortex-M4F)
  • 入出力 : J-Link Lite
  • 開発環境 : Nordic nRF5 SDK 11.0.0

※ Unityは非常に移植性が高いので一例と捉えてもらってOKです。基本的にはputやprintf などキャラクタ出力できる環境があればOKです。

Unity環境準備

ソースコードを下記ページから拝借します

https://github.com/ThrowTheSwitch/Unity

unity_config.h を編集します。環境依存の部分があるのでそこはよしなに設定してください。 ポイントは UNITY_OUTPUT_CHAR(a) の設定です。

#define UNITY_EXCLUDE_STDINT_H
#define UNITY_INCLUDE_DOUBLE
#define UNITY_OUTPUT_CHAR(a)    NRF_LOG_OUTPUT_CHAR(a)
#define UNITY_OUTPUT_START()    NRF_LOG_INIT()
#define UNITY_SUPPORT_WEAK weak __WEAK

編集前ファイル https://github.com/ThrowTheSwitch/Unity/blob/master/examples/unity_config.h

この NRF_LOG_OUTPUT_CHAR のput先に、今回はJLink RTTを指定します。JLink経由でキャラクタ出力できるので非常に便利です。 nRF52の場合は以下のようなファイルを用意しました。

unity_config.c

#include "nrf_log.h"

void NRF_LOG_OUTPUT_CHAR(int c) {
    NRF_LOG_PRINTF("%c",c);
}

これだけです。

あとはプロジェクトファイルに以下ファイルを追加

unity.c
unity_config.c

以下ファイルをincludeパス上に追加

unity_config.h
unity_internals.h

unity_config.hを有効にするためにコンパイルオプションのDefineに以下を追加

UNITY_INCLUDE_CONFIG_H

これでunity環境が整いました。

Unityテスト方法

あとは公式のドキュメントどおりにコーディングすればOKです。

引用: http://www.throwtheswitch.org/unity

以下のように、3つの引数の平均値を計算する関数をテストする場合を想定します。

テスト対象:DumpExample.c

#include "DumbExample.h"

int8_t AverageThreeBytes(int8_t a, int8_t b, int8_t c)
{
return (int8_t)(((int16_t)a + (int16_t)b + (int16_t)c) / 3);
}

テスト対象のヘッダファイル:DumpExample.h

#include <stdint.h>

int8_t AverageThreeBytes(int8_t a, int8_t b, int8_t c);

以下のように UNITY_BEGIN(); と UNITY_END(); の間に RUN_TEST(テスト関数); を入れ込み、 テスト関数内でチェックのためのアサートを書きます。

テストコード:main.c

#include "unity.h"
#include "DumbExample.h"

void test_AverageThreeBytes_should_AverageMidRangeValues(void)
{
TEST_ASSERT_EQUAL_HEX8(40, AverageThreeBytes(30, 40, 50));
TEST_ASSERT_EQUAL_HEX8(40, AverageThreeBytes(10, 70, 40));
TEST_ASSERT_EQUAL_HEX8(33, AverageThreeBytes(33, 33, 33));
}

void test_AverageThreeBytes_should_AverageHighValues(void)
{
TEST_ASSERT_EQUAL_HEX8(80, AverageThreeBytes(70, 80, 90));
TEST_ASSERT_EQUAL_HEX8(127, AverageThreeBytes(127, 127, 127));
TEST_ASSERT_EQUAL_HEX8(84, AverageThreeBytes(0, 126, 126));
}

int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_AverageThreeBytes_should_AverageMidRangeValues);
RUN_TEST(test_AverageThreeBytes_should_AverageHighValues);
return UNITY_END();
}

このコードをコンパイルし、JLinkRTTでウォッチすると以下のような結果が返ってきます。

testUnit1.c:21:test_AverageThreeBytes_should_AverageMidRangeValues:PASS
testUnit1.c:22:test_AverageThreeBytes_should_AverageHighValues:PASS
-----------------------
2 Tests 0 Failures 0 Ignored
OK

レポートはUNITY_BEGIN()からUNITY_END()までのテストがレポートされます。 テストシナリオを区切りたい場合には別のBEGIN/ENDで区切るのがよいでしょう。

まとめ

組込みCテストフレームワークUnityの導入方法、カンタンな使い方の解説をしました。

弊社ではIoT製品が商材ということもあって、ハードウェア/ソフトウェア共に何千万回とテストをしています。 例えばAkerunの開け閉め、Flashの書換、LEDの点灯消灯などを実環境で動かしつつ、一つ一つエラー率などを確認しています。 弊社の開発室にはRPiやArduino、センサ、ソレノイド、モーターなどで作った治具が所狭しと並べられていて、 テストシーズンには常にガチャガチャと治具が動く音が響き渡っています。

Unityはそいういったテスト環境においてカンタンに導入でき、テストコードの保守性、生産性をアップさせる非常に強力なツールです。 同じような環境の現場の方々、導入を検討してみてはいかがでしょうか?

参考

UnityのHP : http://www.throwtheswitch.org/

Unityのgithubリポジトリ : GitHub - ThrowTheSwitch/Unity: Simple Unit Testing for C

告知

弊社ではエンジニアを募集しています!

www.wantedly.com