/Test_you

電子工作やプログラミングなど、やってみたことのメモ

RXマイコンで、Unityによる単体テストをやってみた

Qiitaの「TDDによるマイコンのLチカ開発」を、ルネサス製のRXマイコンIDE(e2studio)の組み合わせで、真似してみました。

  1. RXマイコンで、Unityを使って単体テストをしてみる。
  2. シミュレータ環境、ターゲットボード環境の2通り。
  3. 元の記事にあった、ホスト環境は省略。
  • Unityを使った単体テスト環境のセットアップについては、こちら
  • プロジェクト一式は、こちら(rx231_unit_test_2.zip - Google ドライブ)
    インポート方法
    1. メニュー >ファイル > インポート でインポートのダイアログを表示
    2. 既存プロジェクトをワークスペースへを選択
    3. アーカイブ・ファイルの選択でファイルを選択し、終了ボタンでインポート

環境

お題

ハードウェアを絡めた一番簡単なお題を設定します。ターゲットボードには、LED2つとSW1つがあります。これを利用して、次の機能を持つ関数IO_CotrolLedBySw()を作ります。

  • SW1 を押すと、LED0が点灯
  • SW1 を離すと、LED0が消灯

LEDとSWの仕様は下記の通りです。

port name dir L H
PD6 LED0 out 点灯 消灯
PD7 LED1 out
PB1 SW1 in 押す 離す

シミュレータ環境でのテスト

1. テストファイルを用意する

以前の記事で、下記のテストファイルを/srt/test/以下に用意しています。

  • Test0.c - テストグループの定義、および、各テストケースを記述
  • AllTests.c - テストで実行するテストグループを記述

このうち、Test0.cを修正してテストを用意します。

シミュレータ環境の特徴として、ReadOnlyの入力データレジスタ(PIDR)にも書き込み可能です。
このため、SW1 のボタンを押した状態にするには、

// SW1 のボタンを押した状態にする
SW1 = SW1_PUSH;           // PORTB.PIDR.BIT.B1 = 0;

で済みます。これを利用すると、テストコードは下記になります。

#include "../unity/unity_fixture.h"
#include "../io.h"

・・・

// テストケース(IO_CotrolLedBySw() - SW1 を押すと、LED0が点灯)
TEST(Test0, IO_CotrolLedBySw_SW1Push)
{
    // まず、LED0をOFFしておく
    LED0 = LED_OFF;

    // SW1 のボタンを押した状態にする
    // シミュレータ環境では、入力データレジスタに値を書き込める
    SW1 = SW1_PUSH;

    // テスト対象関数を呼ぶ
    IO_CotrolLedBySw();

    // LED0がONに変化した事をチェック
    TEST_ASSERT_EQUAL(LED_ON, LED0);
}

// テストケース(IO_CotrolLedBySw() - SW1 を離すと、LED0が消灯)
TEST(Test0, IO_CotrolLedBySw_SW1Release)
{
    // まず、LED0をONしておく
    LED0 = LED_ON;

    // SW1 のボタンを離した状態にする
    SW1 = SW1_RELEASE;

    // テスト対象関数を呼ぶ
    IO_CotrolLedBySw();

    // LED0がOFFに変化した事をチェック
    TEST_ASSERT_EQUAL(LED_OFF, LED0);
}

// テストグループで、実行するテストケースを列挙する
TEST_GROUP_RUNNER(Test0)
{
    RUN_TEST_CASE(Test0, IO_CotrolLedBySw_SW1Push);
    RUN_TEST_CASE(Test0, IO_CotrolLedBySw_SW1Release);
}

2. テスト対象コードの用意

実際はTDD的に実装を進めるのですが、記事の都合上、最終コードを掲載します。

#ifndef IO_H_
#define IO_H_
#include "r_smc_entry.h"    // iodefine.h を使用するために記載

#define LED_ON          (0)
#define LED_OFF         (1)
#define SW1_PUSH        (0)
#define SW1_RELEASE     (1)

/* Switches */
#define SW1             (PORTB.PIDR.BIT.B1)

/* LED port settings */
#define LED0            (PORTD.PODR.BIT.B6)
#define LED1            (PORTD.PODR.BIT.B7)

void IO_CotrolLedBySw(void);

#endif /* IO_H_ */
#include "io.h"

void IO_CotrolLedBySw(void)
{
    if ( SW1 == SW1_RELEASE ) {
        LED0 = LED_OFF;
    } else {
        LED0 = LED_ON;
    }
}

3. シミュレータ環境でのテスト実行

  • ターゲットボードをPCから外しておく
  • ビルド構成としてDebug(Debug)を設定し、ビルド
  • デバックの構成として~ Debugを選択し、デバック開始&プログラム実行
  • Renesas Debug Virtual Consoleに下記が表示されればOK
Unity test run 1 of 1
TEST(Test0, IO_CotrolLedBySw_SW1Push) PASS
TEST(Test0, IO_CotrolLedBySw_SW1Release) PASS

-----------------------
2 Tests 0 Failures 0 Ignored 
OK

ターゲットボード環境でのテスト

1. テストファイルを用意する

ターゲットボード環境では、SW1 を人に操作してもらいます。このため、メッセージを表示する補助関数を用意します。

// 手動操作の補助関数
static void ManualOperation(const char *operation){
    printf("\nManual Operation> %s -> press <y>: ", operation);
    getchar();
}

これを利用すると、テストコードは下記になります。

USE_SIMULATORはシミュレータ環境のみ定義されるマクロです。

#include "../unity/unity_fixture.h"
#include "../io.h"

・・・

// テストケース(IO_CotrolLedBySw() - SW1 を押すと、LED0が点灯)
TEST(Test0, IO_CotrolLedBySw_SW1Push)
{
    // まず、LED0をOFFしておく
    LED0 = LED_OFF;

    // SW1 のボタンを押した状態にする
#ifdef USE_SIMULATOR
    // シミュレータ環境では、入力データレジスタに値を書き込める
    SW1 = SW1_PUSH;
#else
    // ターゲットボード環境では、手動でSW1を操作し、状態をチェックして進む
    do {
        ManualOperation("push sw1.");
    } while( !(SW1 == SW1_PUSH) );
#endif

    // テスト対象関数を呼ぶ
    IO_CotrolLedBySw();

    // LED0がONに変化した事をチェック
    TEST_ASSERT_EQUAL(LED_ON, LED0);
}

// テストケース(IO_CotrolLedBySw() - SW1 を離すと、LED0が消灯)
TEST(Test0, IO_CotrolLedBySw_SW1Release)
{
    // まず、LED0をONしておく
    LED0 = LED_ON;

    // SW1 のボタンを離した状態にする
#ifdef USE_SIMULATOR
    SW1 = SW1_RELEASE;
#else
    do {
        ManualOperation("release sw1.");
    } while( !(SW1 == SW1_RELEASE) );
#endif

    // テスト対象関数を呼ぶ
    IO_CotrolLedBySw();

    // LED0がOFFに変化した事をチェック
    TEST_ASSERT_EQUAL(LED_OFF, LED0);
}

// テストグループで、実行するテストケースを列挙する
TEST_GROUP_RUNNER(Test0)
{
    RUN_TEST_CASE(Test0, IO_CotrolLedBySw_SW1Push);
    RUN_TEST_CASE(Test0, IO_CotrolLedBySw_SW1Release);
}

2. ターゲットボード環境でのテスト実行

  • ターゲットボードをPCに接続しておく
  • ビルド構成 としてHardwareDebug(Debug on hardware)を選択し、ビルド
  • デバックの構成 として~ HardwareDebugを選択し、デバック開始&プログラム実行
  • Renesas Debug Virtual Consoleの表示に従い、SW操作とキーボードを押下し、テストを進める。
  • 最終的に、下記表示となればOK。
Unity test run 1 of 1
TEST(Test0, IO_CotrolLedBySw_SW1Push)
Manual Operation> push sw1. -> press <y>: y PASS
TEST(Test0, IO_CotrolLedBySw_SW1Release)
Manual Operation> release sw1. -> press <y>: y PASS

-----------------------
2 Tests 0 Failures 0 Ignored 
OK

3. 作った関数を動作させる

メイン関数を書き換えて、関数IO_CotrolLedBySw()を呼び出します。 ターゲットボード環境でビルド・実行。SW1押下に応じて、LED0が点灯・消灯すればOKです。

#include "r_smc_entry.h"
#include "io.h"

void main(void)
{
    LED0 = LED_OFF;
    LED1 = LED_OFF;
    while(1) {
        IO_CotrolLedBySw();
    }
}

最後に

実際に試すことで、シミュレータ環境とターゲットボード環境の差が理解できました。シミュレータ環境では、ReadOnlyのレジスタにライト可能であり、入力信号を任意に設定にできます。同様に、CPUペリフェラルのエラーも任意に設定できるので、テスト作成で大きな助けとなりそうです。
このレジスタ設定の自由度は、Unity作者様の記事"WHICH BUILD METHOD?"でも、シミュレータ環境のメリットとして紹介されています。ただ、ターゲットボード環境も、ハードウェアの動作確認やエージングで活用できるのでは?と思っています。

参考にした情報

  1. Qiita - @iwatake2222 さん

    ⇒ 今回やってみる動機となった記事です。

  2. Throw The Switch - Unity作者様のサイト

    単体テスト環境として、シミュレータ、ホスト(Windowsなど)、ターゲットボードを比較しています。

  3. 書籍 - テスト駆動開発による組み込みプログラミング―C言語とオブジェクト指向で学ぶアジャイルな設計