can't go to sleep

組み込み開発の多くをRubyで自動化することに興味があります

組み込みC言語用の単体テストのテクニックについて

ざっくり箇条書きしておきます。

  • レジスタアクセス関連は全てpragma inlineでビルド時に自動展開される関数化して、ヘッダファイル内に定義する。こうすることで、製品コードでのビルド時には直接マクロを呼ぶ場合と同じアセンブラに展開される。更に、レジスタアクセス関連のヘッダファイルのincludeを、ifdefで切って単体テスト時にはfff.hで作成したモック関数を呼ぶようにする。ざっくり、以下のような感じ。
// source.c
#ifdef _UNITY_UNIT_TEST_
#include "testMacro.h"
#else
#include "Macro.h"
#endif

void func1(void)
{
  func_macro();
}

// testMacro.h
// プロトタイプ宣言のみ行う。実体はfff.hにてテストケース部に記載する。
void func_macro(void);

// Macro.h
#pragma inline(func_macro)
static void func_macro(void)
{
  REGISTOR.REG1.BIT = 1;
}
  • スタティック関数は、単体テスト時にはグローバル関数になるようにする。具体的には、STATICという型をtypedefして、製品コード時にはstaticに設定して、単体テスト時には何もしないように設定しておく。こうすることで、すべての関数を単体テストできる。
  • C言語では、どういつファイル内の関数をモック関数に置き換えることはできない。そのため、どうしても原理主義的に全ての関数をモックにしてテストをしたい場合は、すべての関数にifdefをつけて、単体テスト時にifdefを切りながらビルドをする必要がある。ただし、現行のUnityにはその機能は無いため、拡張しないといけない。個人的には、そこまでやらないとテストケースが爆発するような関数(ファイル)は、そもそもファイルわけがうまくできていないということなので、ファイルわけを行うことをおススメしたいわけだが・・・。やはり、ファイルにインタフェース関数は2つ(1つは実行関数で1つは初期化関数)にするのが関数(ファイル)設計としては好ましいかと思う。
#ifndef _UT_VOID_FUNC1_VOID_
void func1(void)
{
}
#endif

#ifdef _UT_VOID_FUNC2_INT_
void func2(int a)
{
}
#endif
  • Unityで単体テストをする場合は、基本的には1ファイルごとに分断してテストすることが好ましい。そのため、includeで他ファイルを呼ぶ場合はその必要な関数と変数と定数をテストケース内にて定義する。関数の場合はfffを使って定義して、変数の場合はテストケース内部で実体化して、テスト対象のコードはexternで呼び出す。(定数はどうするか不明。これもテストケース内で記述する?)そのため、テストケースでincludeして良いヘッダファイルは、テスト対象のヘッダファイルとfffとunityのヘッダファイルだけ。ただし、定数だけ定義しているようなヘッダファイルはincludeしてOK。こうなると実は、定数の扱いがなかなか厄介だったりする。この辺りは、ルールを作って、テストケースを自動で作るスクリプトを作りたいところ。というか、それが無いとUnityの導入障壁が高すぎる。