テスト駆動開発による組み込みプログラミング

―C言語とオブジェクト指向で学ぶアジャイルな設計

[cover photo]
TOPICS
発行年月日
PRINT LENGTH
388
ISBN
978-4-87311-614-3
原書
Test Driven Development for Embedded C
FORMAT
Print PDF
Ebook
3,960円
Ebookを購入する
Print
3,960円

本書は、すぐれた組み込みソフトウェアを開発するための手法を豊富なサンプルコードとともに解説する本です。前半では、制約のある組み込み環境でテスト駆動開発を行うための基礎知識とノウハウを懇切丁寧に紹介します。後半では、オブジェクト指向をベースに考え出されたSOLID原則やリファクタリングをC言語に適用し、アジャイルな設計を実現するための方法を示します。さらに、レガシーコードへのテストの追加方法についてもサンプルコードを使って詳細に解説します。日本語版には平鍋健児氏による 「日本語版まえがき」を収録。テスト駆動開発を学びたい、アジャイル開発について知りたい、レガシーコードと日々格闘している、そんなすべての組み込みCプログラマ必携の一冊です。

目次

目次

本書への賞賛の声
日本語版まえがき
ジャック・ガンセルによるまえがき
ロバート・C・マーティンによるまえがき
はじめに

1章 テスト駆動開発
    1.1 なぜTDDが必要か?
    1.2 テスト駆動開発とは何か?
    1.3 TDDの特性
    1.4 TDDのマイクロサイクル
    1.5 TDDのメリット
    1.6 組み込みにとってのメリット

I部 TDDを始めよう
2章 テスト駆動ツールと約束事
    2.1 ユニットテストハーネスとは何か?
    2.2 Unity:C専用テストハーネス
        2.2.1 Unityによるsprintf()テストケース
        2.2.2 Unityのテストフィクスチャ
        2.2.3 Unityテストのインストール
        2.2.4 Unityの出力
    2.3 CppUTest:C++ユニットテストハーネス
        2.3.1 CppUTestによるsprintf()テストケース
        2.3.2 CppUTestによるsprintf()テストフィクスチャ
        2.2.3 CppUTestの出力
    2.4 ユニットテストはクラッシュする
    2.5 4フェーズテストパターン
    2.6 まとめ
    2.7 練習問題

3章 Cモジュールにとりかかる
    3.1 テストしやすいCモジュール
    3.2 LEDドライバは何をするのか?
    3.3 テストリストを書く
    3.4 最初のテストを書く
        3.4.1 ドライバをだます
        3.4.2 依存性の注入
        3.4.3 テストの前にコードを書いてはいけない
    3.5 内部の前にインターフェイスをテスト駆動する
        3.5.1 実装が間違っている!
        3.5.2 テストは正しい
        3.5.3 次のテストを選ぶ
    3.6 インクリメンタルな進歩
        3.6.1 DTSTTCPWでいこう:まず仮実装、それから本実装
        3.6.2 テストは小さく、的を絞って
        3.6.3 グリーンでリファクタリング
    3.7 テスト駆動開発者のステートマシン
    3.8 テストはFIRST
    3.9 まとめ
    3.10 練習問題

4章 完了までの道のりを確かめる
    4.1 シンプルなところから成長させる
        4.1.1 着実に前進する
        4.1.2 境界条件をテストする
        4.1.3 実行可能なリマインダ
    4.2 コードをきれいに保つ――進みながらリファクタリング
        4.2.1 カットせずに、コピーしよう
        4.2.2 一度にひとつの問題を解決する
    4.3 完了するまで繰り返す
    4.4 「完了」を宣言する前に一歩戻る
    4.5 まとめ
    4.6 練習問題

5章 組み込みTDD戦略
    5.1 ハードウェアボトルネック
    5.2 デュアルターゲットのメリット
    5.3 デュアルターゲットテストのリスク
    5.4 組み込みTDDのサイクル
    5.5 デュアルターゲットの非互換性
        5.5.1 ランタイムライブラリにはバグがある
        5.5.2 互換性のないヘッダファイル
    5.6 ハードウェアを使ったテスト
        5.6.1 自動ハードウェアテスト
        5.6.2 部分的自動ハードウェアテスト
        5.6.3 外部装置を使ったテスト
    5.7 急がば回れ
    5.8 まとめ
    5.9 練習問題

6章 そうは言っても
    6.1 時間がない
        6.1.1 手動テスト
        6.1.2 カスタムテストハーネス
        6.1.3 シングルステップ実行によるユニットテスト
        6.1.4 ドキュメント化とレビューによるユニットテストプロセス
        6.1.5 ユニットテストにかけたコストはどこへいくのか?
    6.2 コードを書いてからテストを書いてはいけないのか?
    6.3 テストをメンテナンスする必要がある
    6.4 ユニットテストがすべてのバグを見つけられるわけではない
    6.5 ビルドに時間がかかる
    6.6 既存のコードがある
    6.7 メモリに制約がある
    6.8 ハードウェアとやりとりする必要がある
    6.9 CをテストするのになぜC++テストハーネスを使うのか?
    6.10 まとめ
    6.11 練習問題

II部	コラボレータのあるモジュールをテストする
7章 テストダブルの導入
    7.1 コラボレータ
    7.2 依存関係を断ち切る
    7.3 いつテストダブルを使うか
    7.4 Cでだますにはどうするか
    7.5 まとめ
    7.6 練習問題

8章 プロダクトコードをスパイする
    8.1 ライトスケジュール機能のテストリスト
    8.2 ハードウェアとOSとの依存関係
    8.3 リンカによる置き換え
    8.4 テスト対象コードをスパイする
    8.5 時計をコントロールする
    8.6 0で動かしてから1で動かす
        8.6.1 リファクタリングして重複を取り除く
        8.6.2 責務をリファクタリングする
        8.6.3 テストをリファクタリングする
        8.6.4 複雑な条件ロジック
        8.6.5 つながりをテストする
    8.7 複数で動くようにする
    8.8 まとめ
    8.9 練習問題

9章 テストダブルの実行時バインディング
    9.1 ランダムなものをテストする
    9.2 関数ポインタを使ってフェイクする
    9.3 スパイを外科的に埋め込む
    9.4 スパイを使って出力を検証する
    9.5 まとめ
    9.6 練習問題

10章 モックオブジェクト
    10.1 フラッシュドライバ
    10.2 MockIO
    10.3 ドライバをテスト駆動で開発する
    10.4 デバイスのタイムアウトをシミュレートする
    10.5 それだけの価値があるのか?
    10.6 CppUMockを使う
    10.7 モックを生成する
    10.8 まとめ
    10.9 練習問題

III部 設計と継続的改善
11章 SOLIDで柔軟でテストしやすい設計
    11.1 SOLID原則
        11.1.1 単一責務の原則(SRP:Single Responsibility Principle)
        11.1.2 オープン・クローズドの原則(OCP:Open Closed Principle)
        11.1.3 リスコフの置換原則(LSP:Liskov Substitution Principle)
        11.1.4 インターフェイス分離の原則(ISP:Interface Segregation Principle)
        11.1.5 依存関係逆転の原則(DIP:Dependency Inversion Principle)
    11.2 SOLIDな設計モデル
        11.2.1 シングルインスタンスモジュール
        11.2.2 マルチインスタンスモジュール
    11.3 要求の進化と問題のある設計
        11.3.1 LightControllerのインターフェイス
        11.3.2 特化したLightDriver
        11.3.3 問題のあるswitch文
        11.3.4 セーフティネットとしてのテスト
    11.4 動的インターフェイスを使って設計を改善する
        11.4.1 オープン・クローズドの原則(OCP)とリスコフの置換原則(LSP)を適用する
        11.4.2 テストにおける変更箇所を考える
        11.4.3 関数ポインタのインターフェイス
        11.4.4 ドライバは変化しない
        11.4.5 NULLポインタに対する防御
        11.4.6 Switch文を取り除く
        11.4.7 詳細を隠蔽する
    11.5 型による動的インターフェイスを使って柔軟性を高める
        11.5.1 2つの異なるドライバによるテスト
        11.5.2 呼び出し回数をカウントするテストダブル
        11.5.3 テスト駆動でvtableを追加する
    11.6 どれだけ設計すれば十分なのか?
        11.6.1 XPのシンプルな設計ルール
    11.7 まとめ
    11.8 練習問題

12章リファクタリング
    12.1 ソフトウェアの2つの価値
    12.2 3つの重要なスキル
        12.2.1 まずいコードを嗅ぎ分ける
        12.2.2 もっとすぐれたコードを思い描く
        12.2.3 コードを変換する
    12.3 コードのにおいと改善方法
        12.3.1 重複したコード
        12.3.2 まずい名前
        12.3.3 まずいパスタ
        12.3.4 長すぎる関数
        12.3.5 抽象化の乱れ(Abstraction Distraction)
        12.3.6 当惑させるブーリアン(Bewildering Boolean)
        12.3.7 恥ずかしいSwitch Case(Switch Case Disgrace)
        12.3.8 重複したSwitch Case
        12.3.9 非道なネスト(Nefarious Nesting)
        12.3.10 機能の横恋慕(Feature Envy)
        12.3.11 長すぎるパラメータリスト
        12.3.12 行き当たりばったりの初期化(Willy-nilly Initialization)
        12.3.13 野放しのグローバル変数(Global Free-for-All)
        12.3.14 コメント
        12.3.15 コメントアウトされたコード
        12.3.16 条件コンパイル
    12.4 コードを変換する
        12.4.1 こうだったらよかったのにと思うコードを思い描く
        12.4.2 シグネチャを評価する
        12.4.3 橋を燃やすな原則(Don't Burn Bridges Principle)
        12.4.4 抽象化の乱れを避ける
        12.4.5 重複を取り除く
        12.4.6 目的を分離する
        12.4.7 当惑させるブーリアンの整理
        12.4.8 クイックスワップ(Quick Swap)
        12.4.9 関数の移動
        12.4.10 ソースファイルの分割
        12.4.11 移動した関数にテストを追加する
        12.4.12 インクリメンタルな切り替え
        12.4.13 データ構造のカプセル化
    12.5 でもパフォーマンスとサイズは?
        12.5.1 動くようにして、正しくして、それから速くする
        12.5.2 最適化の専門家による意見
        12.5.3 マイクロ最適化に気をつける
        12.5.4 パフォーマンステスト
    12.6 まとめ
    12.7 練習問題

13章 レガシーコードにテストを追加する
    13.1 レガシーコードの変更ポリシー
    13.2 ボーイスカウトの原則(Boy Scout Principle)
        13.2.1 長すぎる関数
        13.2.2 複雑な条件分岐
        13.2.3 コピー、ペースト、少し変更の誘惑
        13.2.4 暗号めいたローカル変数名
        13.2.5 深いネスト
    13.3 レガシーコードの変更手順
        13.3.1 変更ポイントを特定する
        13.3.2 テストポイントを見つける
        13.3.3 依存関係を断ち切る(べきか否か)
        13.3.4 テストを書く
        13.3.5 変更とリファクタリングを行う
    13.4 テストポイント
        13.4.1 継ぎ目(Seam)
        13.4.2 グローバル変数
        13.4.3 検出用変数(Sensing Variable)
        13.4.4 デバッグ出力検出ポイント(Debug Output Sense Point)
        13.4.5 インラインモニター(Inline Monitor)
    13.5 構造体の2段階初期化
    13.6 クラッシュさせて成功させる(Crash to Pass)
        13.6.1 コンパイルを成功させる
        13.6.2 リンクを成功させる
        13.6.3 テストがクラッシュする
        13.6.4 実行時の依存関係を見つける
        13.6.5 実行時の依存関係を解決する
        13.6.6 レガシーコードにさらにテストを追加する
        13.6.7 テストは現在の要求を満たしている
        13.6.8 直前のテストをコピー&ペーストして少し変更する
        13.6.9 テストケースの違いがよくわかる
        13.6.10 準備フェーズがよく似ている
        13.6.11 共通のセットアップを抽出する
        13.6.12 検証フェーズがよく似ている
        13.6.13 共通のアサーションを抽出する
        13.6.14 新しいテストグループが必要か考える
        13.6.15 exit(SUCCESS)
    13.7 仕様化テスト(Characterization Test)
    13.8 サードパーティ製コードのための学習テスト
    13.9 テスト駆動によるバグ修正
    13.10 戦略的テストを追加する
    13.11 まとめ
    13.12 練習問題

14章 テストのパターンとアンチパターン
    14.1 「だらだら続くテスト」アンチパターン
    14.2 「コピー、ペースト、少し変更、繰り返し」アンチパターン
    14.3 「場違いなテストケース」アンチパターン
    14.4 「テストグループ間の重複」アンチパターン
    14.5 「テスト軽視」アンチパターン
    14.6 「振る舞い駆動開発」テストパターン
    14.7 まとめ
    14.8 練習問題
最後に

IV部 付録
付録A ホスト開発システム上のテスト環境
    A.1 ホスト開発システムのツールチェーン
        A.1.1 Linux上での開発
        A.1.2 Apple Mac上での開発
        A.1.3 Windows上での開発
    A.2 フルテストビルド用のmakefile
    A.3 テストビルドを小さくする

付録B Unityクイックリファレンス
    B.1 テストファイル
    B.2 テストmain
    B.3 TEST条件チェック
    B.4 コマンドラインオプション
    B.5 ターゲット上でのUnity

付録C CppUTestクイックリファレンス
    C.1 テストファイル
    C.2 テストmain
    C.3 TEST条件チェック
    C.4 テストの実行順序
    C.5 スケルトン実装作成用スクリプト
    C.6 ターゲット上でのCppUTest
    C.7 CppUTestのテストをUnityに変換する

付録D 作成中のLedDriver
    D.1 LedDriverの最初のUnityテスト
    D.2 LedDriverの最初のCppUTestテスト
    D.3 初期LedDriverのインターフェイス
    D.4 LedDriverのスケルトン実装

付録E OS分離レイヤーの例
    E.1 置き換え可能な振る舞いを保証するためのテストケース
    E.2 POSIX実装
    E.3 Micrium RTOS実装
    E.4 Win32実装
    E.5 負担はアプリケーションではなくレイヤーに引き受けさせる

付録F 参考文献

監訳者あとがき
索引