0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【単体テスト#2】C言語ツール選定編 ― 商用ツール・GoogleTest・Unity 徹底比較

0
Posted at

このシリーズはC言語組み込み開発における単体テストを体系的に整理することを目的としています。

  • #1:概念・テストレベル・手法・テストダブル(スタブ・モック・フェイク)
  • #2(本記事):C言語ツール選定 ― 商用ツール・GoogleTest・Unity 徹底比較
  • #3:TDD・CI/CD ― テスト自動化で品質担保と工数削減を実現する

本記事では、スタブ・モック・フェイク(テストダブル)の概念を前提として扱う。概念が曖昧な場合は[#1]をあわせて参照されたい。


1. 商用ツール

カバレッジマスターwinAMS(ガイオ・テクノロジー)

国産の組み込みC/C++向け単体テストツール。最大の特徴は、クロスコンパイラで生成した実装マイコンコードをそのままマイコンシミュレータ上で実行してテストを行う点にある。一般的なツールのようにCソースの論理レベルでテストするのではなく、コンパイラの最適化やマイコン固有の実装に依存した問題まで含めて検出できる。

C0/C1/MC/DCカバレッジに加え、ISO 26262の結合テストで求められる関数カバレッジ・コールカバレッジの計測にも対応。TÜV SÜDよりISO 26262およびIEC 61508のツール認証を取得しており、自動車制御ソフトの分野ではデファクトスタンダード的な位置づけとなっている。プログラム解析ツール「CasePlayer2」との連携でテストデータ設計も支援する。

  • メリット:実コードベースのテストで組み込み固有の問題を検出できる、国産ゆえサポートが日本語で手厚い、機能安全規格の認証取得済み
  • デメリット:対応マイコンの制約がある、ライセンスコストがかかる

Parasoft C/C++test(Parasoft)

静的解析・単体テスト・実行時エラー検出・コードレビューを統合したC/C++向けの開発テストプラットフォーム。Eclipse・Visual StudioのIDEプラグインとして動作し、コマンドラインからのバッチ実行にも対応するためCIへの組み込みが容易。

単体テストの自動生成機能を持ち、カバレッジデータはParasoft DTP(Development Testing Platform)のWebダッシュボードで集約・可視化できる。クロスプラットフォームおよび組み込みテストにも対応しており、TÜV SÜDによるIEC 61508・ISO 26262のツール認証を取得済み。

  • メリット:静的解析から単体テストまで一つのツールでカバー、CI連携が強力、IDE統合で開発者が使いやすい
  • デメリット:機能が多い分、導入・設定の初期コストがかかる

VectorCAST(Vector Informatik GmbH)

組み込みC/C++向けの統合テスト環境。テストケース自動生成、カバレッジ計測(C0/C1/MC/DC)、要件トレーサビリティをGUIで一元管理できる。ISO 26262やA-SPICEへの適合支援機能を持ち、自動車・航空・産業機器分野で国際的に広く使われる。

  • メリット:国際的な実績が豊富、規格対応が手厚い、レポート出力が整備されている
  • デメリット:ライセンスコストが高め、日本語サポートは限定的な場合がある

現場の実態:商用ツールはコストと引き換えに、規格適合の工数削減と品質の可視化という大きなリターンが得られる。プロジェクトの安全要件・規模・チームのスキルセットを踏まえて選定したい。


2. マイコンシミュレータ型 vs ホスト環境型 ― 根本的な違いを理解する

GoogleTestやUnityなどのOSSフレームワークと、winAMSのようなマイコンシミュレータ(ISS)を用いた商用ツールは、単体テストというカテゴリに属していながら、テストの実行モデルが根本的に異なる。この違いを理解せずに選定すると、「テストは書いたが、実機で想定外の動作が起きた」という事態につながりかねない。

テスト実行モデルの違い

【ホスト環境型(GoogleTest / Unity)】

  Cソース(対象関数)+ テストコード
    ↓  ホスト向けコンパイラ(x86_64 gcc / clang)でビルド
  x86_64バイナリ
    ↓
  ホストPC上でそのまま実行 → テスト結果

  ※ホスト向けコンパイラが生成したコードを実行する
  ※Cソースの論理レベルのみを検証する
  ※クロスコンパイラでビルドしたARMバイナリはホストでは実行不可
    (アーキテクチャが異なるため "Exec format error" になる)


【マイコンシミュレータ型(winAMS)】

  Cソース(対象関数)
    ↓  クロスコンパイラ(arm-none-eabi-gcc 等)でビルド
  マイコン向けオブジェクトコード(実装マイコンコード)
    ↓
  内蔵マイコンシミュレータ(ISS)がARMバイナリをホストPC上でエミュレート実行
    → テスト結果

  ※最終製品に使うコードそのものを実行する
  ※ISSがあるからこそ、ARMバイナリをホストPCで動かせる
  ※コンパイラ最適化・アーキテクチャ依存の挙動も含めて検証できる

検出できる問題の範囲

ホスト環境型はCソースの論理レベルのみを検証する。コード中のアルゴリズム・分岐・入出力の正しさを確認するには十分だが、以下のような問題は原理的に検出できない。

  • コンパイラ最適化による挙動の変化:ターゲット向けクロスコンパイラが -O2 などの最適化フラグを使った結果、ソースの論理とオブジェクトコードの挙動がずれるケース
  • マイコンのアーキテクチャ依存バグ:データ型のサイズ(int が16bitか32bitか)、エンディアン差異、アラインメント制約など
  • コンパイラ不具合:稀ではあるが、クロスコンパイラ自体のバグが原因で生成コードが誤る場合

マイコンシミュレータ型はクロスコンパイラが生成したオブジェクトコードをそのままISSで実行するため、こうした問題を単体テストの段階で検出できる。

カバレッジ計測の精度の違い

ホスト環境でのカバレッジ計測(gcov等)はCソースの行・分岐単位での計測となる。一方、winAMSはマイコン向けオブジェクトコードを実行してカバレッジを計測するため、コンパイラが最適化でコードを変形した結果として生じる「ソース上はカバーしているが実コードでは通っていない」ケースを検出できる。

コスト・導入コストの違い

観点 ホスト環境型(OSS) マイコンシミュレータ型(商用)
ライセンスコスト 無償 高い(数十〜数百万円/年規模)
環境構築コスト 低〜中(CMakeなど) 中〜高(対応マイコン設定など)
検証対象コード Cソースの論理 実装マイコンコード(オブジェクト)
コンパイラ依存バグの検出
アーキテクチャ依存バグの検出 ✕(ホストと同一アーキテクチャで動くため)
CI/CDへの組み込みやすさ ◎(コマンドライン一発) △(ライセンスサーバ・環境依存)
規格対応エビデンス出力 △(スクリプト整備が必要) ◎(自動出力機能あり)
ターゲット上での実行 ✕(ホスト向けバイナリのみ実行可) ◎(ISSがARMバイナリをホストPCでエミュレート)

どう使い分けるか

両者は競合するものではなく、テストの目的と確認したいリスクによって使い分けるのが現実的な考え方。

ホスト環境型(GoogleTest / Unity)が適する場面:

  • アルゴリズム・制御ロジックなどの純粋な論理検証
  • 開発初期からTDDでロジックを固めたい場合
  • CI/CDへの自動統合を優先する場合
  • コストを抑えてまず単体テスト文化を根付かせたい場合

マイコンシミュレータ型(winAMS等)が適する場面:

  • 機能安全規格(ISO 26262 / IEC 61508)への適合が必要な場合
  • コンパイラの最適化や特定マイコンアーキテクチャへの依存が懸念される場合
  • テスト結果・カバレッジのエビデンスを規格準拠の形式で自動出力する必要がある場合
  • 実機を調達できない段階から実コードに近い検証を行いたい場合

現場の視点:「OSSで十分かどうか」の判断軸は、安全要件の有無コンパイラ・アーキテクチャへの依存リスクをどこで吸収するかにある。安全要件のないプロジェクトならGoogleTest/Unityで十分なことが多い。機能安全対応プロジェクトでは「Cソースの論理はOSSでテスト、実コードの振る舞いはwinAMSで確認」という二段構えも選択肢になる。


3. GoogleTest

GoogleTestはGoogle製のC++テストフレームワークだが、CのコードをC++からテストするパターンで組み込み開発にも活用される

基本的な使い方

// 対象関数(C言語)
// calc.h
int add(int a, int b);

// テストコード(C++)
#include <gtest/gtest.h>
extern "C" {
#include "calc.h"
}

TEST(CalcTest, AddPositive) {
    EXPECT_EQ(add(2, 3), 5);
}

TEST(CalcTest, AddNegative) {
    EXPECT_EQ(add(-1, -1), -2);
}

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

モック(依存関係の置き換え)

GoogleMockを使うことで、ハードウェアドライバなどの依存コンポーネントをモックに差し替えてテストできる。

class MockHAL : public HALInterface {
public:
    MOCK_METHOD(uint32_t, readRegister, (uint32_t addr), (override));
};

組み込みでの注意点

  • ホストPC(Linux/Windows)上でx86_64向けにビルドして実行するのが基本
  • 標準ライブラリへの依存があるため、ターゲット上での直接実行は難しい
  • ホスト環境でのロジックテスト → 実機でのHILテストという役割分担が現実的

4. Unity(ThrowTheSwitch)

UnityはC言語専用の軽量単体テストフレームワーク。組み込み向けに設計されており、標準Cのみで動作する。

特徴

  • ヘッダ1つ・ソース1つで導入できるシンプルな構成
  • printf 相当の出力さえあればどんな環境でも動く
  • CeedlingというRakeベースのビルドシステムと組み合わせて使われることが多い

基本的な使い方

#include "unity.h"
#include "calc.h"

void setUp(void) {}
void tearDown(void) {}

void test_add_positive(void) {
    TEST_ASSERT_EQUAL_INT(5, add(2, 3));
}

void test_add_negative(void) {
    TEST_ASSERT_EQUAL_INT(-2, add(-1, -1));
}

int main(void) {
    UNITY_BEGIN();
    RUN_TEST(test_add_positive);
    RUN_TEST(test_add_negative);
    return UNITY_END();
}

FFF(Fake Function Framework)との組み合わせ

依存関数のフェイク(スタブ)生成にはFFFが定番。

#include "fff.h"
DEFINE_FFF_GLOBALS;
FAKE_VALUE_FUNC(uint32_t, HAL_ReadRegister, uint32_t);

void test_something(void) {
    HAL_ReadRegister_fake.return_val = 0xDEADBEEF;
    // ... テスト本体
}

5. GoogleTest vs Unity ― 組み込み現場での比較評価

実際にどちらを選ぶかは「何をテストしたいか」「チームのスキルセット」「環境制約」の三点で決まる。以下、観点ごとに整理する。

環境構築の容易さ

Unity が有利。

Unityの構成はCソース1つ・ヘッダ2つとシンプルで、リンクの手間が少ない。既存のCビルド環境にファイルを追加するだけで動き始めるため、組み込みチームには大きなメリット。C言語の知識だけで完結する点も重要。

GoogleTestはC++のビルドチェーンとCMakeが必要で、クロスコンパイラ環境によっては標準ライブラリの扱いで詰まることがある。ビルド時に -lpthread-lgtest-lgtest_main の指定が必要になるなど、初期セットアップのハードルはUnityより高い。

用途・適用範囲

観点 GoogleTest Unity
テスト記述言語 C++(C関数は extern "C" でラップ) 純C
ビルドに使うコンパイラ ホスト向けgcc / clang(x86_64) ホスト向けgcc / clang(x86_64)
テスト実行環境 ホストPC上でそのまま実行 ホストPC上でそのまま実行
クロスコンパイルしたARMバイナリの実行 ✕(x86_64ホストでは実行不可) ✕(同左)
C言語チームへの親しみやすさ C++の知識が前提 C言語の知識で完結

重要な整理:GoogleTest・Unityはどちらも「ホスト向けコンパイラでビルドしてホストPC上で実行する」ツールである。

クロスコンパイルしたARMバイナリをホストPC上で実行してテストするには、ISS内蔵の商用ツール(winAMS・Parasoft C/C++test)が必要になる点はどちらのフレームワークでも変わらない。

モック・スタブの実現手段(フレームワーク別)

GoogleTest(+GoogleMock):

GoogleMockのモック機能はC++の仮想関数向けに設計されており、C言語の関数を直接モック化することはできない。C関数をモック化する手段としては、関数ポインタを使った迂回パターンのほか、weak symbolやLD_PRELOADを使ったリンク時差し替えも現場では用いられる。

// 関数ポインタ経由でモックに差し替えるパターン
int (*HAL_ReadTemperature_ptr)(void) = HAL_ReadTemperature;

// テスト内で差し替え
int mock_temp(void) { return 90; }
HAL_ReadTemperature_ptr = mock_temp;

Unity+CMock:

CMockはCのヘッダファイルを読み込んでモックコードを自動生成するRubyスクリプト。Ceedlingと組み合わせると ceedling generate:mock 一発でモックファイルが生成される。デメリットはRubyの実行環境が必要な点。

ceedling generate:mock hal_temperature
# → mock_hal_temperature.c / mock_hal_temperature.h が生成される

FFF(Fake Function Framework):

Ruby不要・ヘッダ1つで使える軽量なC専用モックフレームワーク。マクロでモック関数を定義するスタイルで、Ceedlingを使わない素のUnity環境とも組み合わせやすい。

#include "fff.h"
DEFINE_FFF_GLOBALS;
FAKE_VALUE_FUNC(int, HAL_ReadTemperature);
FAKE_VOID_FUNC(HAL_WriteRegister, uint32_t, uint32_t);
【C言語でのモック実現手段まとめ】

  GoogleMock(GoogleTest)→ C++仮想関数向け。C関数には関数ポインタ迂回が必要。
  CMock(Unity連携)      → Cヘッダからモック自動生成。Ruby必要。大規模向き。
  FFF(Unity / 単独利用) → マクロで定義。Ruby不要・依存ゼロ。シンプルさが強み。
  手書きスタブ            → ツール不要。小規模・単純な依存なら最もシンプル。

運用面・チーム継続性

Unityは構造がシンプルな分、テストコードの読みやすさ・引き継ぎやすさに優れる。C言語の基礎知識さえあれば新規参入者でも即戦力になれる点は、組み込み現場の人員流動を考えると重要。

GoogleTestはCMakeやC++ビルド環境の知識が前提になるため、C専業エンジニアが多い現場では「テストを書けるが環境を直せない」という状況が生まれやすい。CI環境のメンテナンスも含めてC++に習熟したメンバーが最低一人は必要になる。

エビデンスの残し方

GoogleTest:

  • コンソール出力はカラー付きで視認性が高い
  • --gtest_output=xml:結果.xml でJUnit互換のXMLを出力できる
  • CIツール(Jenkins、GitLab CI等)はJUnit XMLを標準でパースしてレポートを自動生成するため、CI連携のエビデンスとしては最も整備されている
  • gcovと組み合わせてカバレッジを計測できる。HTML形式のレポートにはさらにlcovが必要。
# JUnit XML出力
./test_runner --gtest_output=xml:test_results.xml

# カバレッジHTML生成(gcov + lcov)
gcov *.gcda
lcov --capture --directory . --output-file coverage.info
genhtml coverage.info --output-directory coverage_html

Unity(+Ceedling):

  • Ceedlingを使うと ceedling test:all でJUnit XMLやHTMLレポートを自動生成できる
ceedling test:all
ceedling report  # HTMLレポート生成

規格対応のエビデンスが必要な場合:

ISO 26262やIEC 61508への対応エビデンスとして単体テスト結果を提出する場合、テストケースID・入出力値・判定結果を一覧できるトレーサビリティ資料が求められる。OSSツールだけでこれを整備するにはスクリプト開発の手間が生じるため、商用ツール(winAMS・Parasoft C/C++test・VectorCAST)の出番になる。

まとめ:どちらを選ぶか

条件 推奨
C言語専業チーム・C++ビルド環境が整っていない Unity(+FFF or CMock)
ホスト向けgccでビルド+ホストPC上でロジックテスト GoogleTest・Unity ともに対応
クロスコンパイルしたARMバイナリをホストPC上で実行してテスト 商用ツール(winAMS・Parasoft C/C++test)のみ可
CI連携のエビデンスをJUnit XMLで揃えたい GoogleTest(Ceedling+Unityも対応可)
規格対応のトレーサビリティ資料が必要 商用ツール
段階的に導入したい・まず試したい Unity(最小構成が小さい)

おわりに

本記事ではC言語での単体テストツール選定について、商用ツール・GoogleTest・Unityの比較を中心に整理した。次回はTDDとCI/CDによるテスト自動化の実践を扱う。

次の記事
【単体テスト#3】TDD・CI/CD編 ― テスト自動化で品質担保と工数削減を実現する
(公開後にリンクを追記予定)


参考・関連リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?