組み込みCでテスト駆動開発(TDD)によるステートマシン構築
ステートマシン(SM)は多くの組み込みシステムの基盤ですが、その開発は複雑になりがちです。この記事では、リソース制約環境に適したC実装に焦点を当て、テスト駆動開発(TDD)を用いたステートマシンソフトウェア構築の実証済み戦略を概説します。
本質的に、SMモデルは状態、遷移、アクションで構成されます。状態は条件を表し、遷移は通常イベントによってトリガーされる一状態から別状態へのパスを定義し、アクションはエントリー、イグジット、遷移中に実行される振る舞いを取得します。
UMLステートマシンでは、アクションを状態のエントリー/イグジット、遷移自体、内部反応に付加できます。すべてのステートマシン形式はRun-to-Completion(RTC)実行モデルを前提とします:各イベントが完全に処理される(イグジットアクション、遷移アクション、エントリーアクションを含む)まで、次のイベントは処理されません。
TDD戦略に入る前に、なぜTDDが重要かを理解することが不可欠です。TDDは、本番コードを書く前に失敗するユニットテストを書くことを義務付ける漸進的開発アプローチです。テストは小さく自動化され、望ましい振る舞いの仕様として機能します。James Grenningの「Test-Driven Development for Embedded C」に基づくTDDサイクルは以下の通りです:
- 小さな失敗するテストを書く
- すべてのテストを実行;新しいテストが失敗することを確認
- テストをパスする最小限のコードを書く
- 再度すべてのテストを実行し、新しいテストがパスすることを確認
- 重複を排除し明確化するためリファクタリング
図1のSM図を考えてみます。これはStateAで開始し、Alphaイベント受信時にStateBへ遷移し、xStateA()、effect()、nStateB()を順に実行します。この振る舞いが正しいことをどう検証するか?

図1. 基本ステートマシン(出典:VortexMakes)
最も直接的なテストアプローチは、SMの遷移テーブルを検証することです。これにより、状態ごとに1つのテストケースが作成され、関連イベントでSMを刺激し、結果の状態と実行されたアクションをアサートします。複雑なアクションには専用テストが必要です(「Testing state machines using unit test」記事を参照)。
各テストは4フェーズのxUnitパターンに従います:
- Setup – 事前条件の確立(現在の状態、イベント、期待される目標状態、アクションシーケンス)
- Exercise – イベントでSMをトリガー
- Verify – 結果が期待と一致することをアサート
- Cleanup – SMを初期状態にリセット(オプション)
この戦略は単純なSMには有効ですが、効果が数ステップ後にしか観測できない遷移チェーンなど、より複雑な振る舞いには機能シナリオテストが必要です。孤立した状態チェックではなく、必要な状態、イベント、アクションを網羅する完全なシーケンスをテストします。
図2は、従来のdo-whileループを模倣するDoWhile SMでこれを説明しています。このマシンはx = 0とoutput = 0でデフォルト値に初期化し、x++またはx = (x > 0) ? x-: xでループカウンターを設定し、ガードi == xが失敗するまでi++で反復し、その後output = iを出力します。

図2. DoWhileステートマシン(出典:VortexMakes)
コーディング前に、仕様から導かれたテストリストを作成します。DoWhileの初期リストには:
- 初期化時にすべてのデータがデフォルトに設定される
- X属性のインクリメント
- X属性のデクリメント
- 単一反復ループの実行
- 複数反復の実行
- 非反復シナリオの処理
- 範囲外条件のチェック
ビルドシステムにCeedling、Cコード用軽量テストハーネスにUnityを採用。SMはDoWhile.hとDoWhile.cで定義し、テストスイートはtest_DoWhile.cに配置。以下は「単一反復」テストケースの簡潔な抜粋です。

コードリスト1:単一反復テスト(出典:VortexMakes)
テストはSMを初期化し、反復カウントをゼロに設定し、Upイベントをディスパッチしてインクリメントし、Startイベントをトリガーし、最後にAlphaを送信して1ループ反復を実行。アサーションでout属性が反復数と等しく、SMがStateCに留まることを検証します。
同じテストを拡張して複数反復を実証(コードリスト2)。別テスト(リスト3)で、事前のUpイベントなしでAlphaを受信した場合にSMが反復しないことを確認。

コードリスト2:複数反復テスト(出典:VortexMakes)

コードリスト3:非反復テスト(出典:VortexMakes)
実装詳細は本記事の焦点ではありませんが、DoWhile.cとDoWhile.hからの抜粋は、状態処理へのクリーンなswitch-caseアプローチを示しています。

コードリスト4:DoWhile実装の断片(出典:VortexMakes)

コードリスト5:DoWhile仕様の断片(出典:VortexMakes)
まとめると、TDDはステートマシン開発に2つの補完的戦略を提供します:(1) 状態ごとの遷移テスト、および (2) 複数の状態とアクションをカバーする機能シナリオテスト。これらの手法は言語・ツール非依存であり、状態ベースロジックが支配する組み込みシステムに特に適しています。
Cが組み込みソフトウェアの共通語であり続ける限り、CeedlingとUnityとの組み合わせで堅牢なTDDワークフローが得られます。これらのプラクティスを採用することで、従来のアプローチよりも柔軟で保守性が高く、再利用可能なソフトウェアが生まれます。
すべてのコードとモデルはGitHubリポジトリ https://github.com/leanfrancucci/sm-tdd.git で公開されています。
埋め込み
- CNC信頼性向上:最新フライス工具が高速・高精度・低コストを実現
- 2021年製造業トレンド:Make UKからのインサイト─パート2
- Boy Machines:最先端技術で小規模生産を前進
- AI駆動テスト自動化:最新ERPシステムのQAを革新
- Accelerate Project、戦略的アウトソーシングで始動——中小企業・スタートアップの開発力を強化
- 一般的なCNC工作機械問題とエキスパート解決策
- 立形フライス盤マスター:メリット、操作、横形を上回る理由
- 中古CNC工作機械購入で陥りがちな落とし穴と回避策
- 無限の可能性を探る:CNCマシンで何が作れるか
- Okamoto精密研削盤で完璧な仕上げを実現:業界トップクラスの研磨技術
- オークマCNCをアップグレード:フロッピードライブをUSB・Ethernetに置換