2012/04/28

Test Driven Development for Embedded C ぺアプロの会 第1回 演習補足資料


本エントリは、5/6(日)に行う「Test Driven Development for Embedded C ぺアプロの会 第1回」の補足資料です。最初のテストを書くまでが大変になりそうなので、演習をスムーズに進める目的で作成しました。

本エントリの範囲

第8章 p.138 「8.4 Spying on the Code Under Test」までです。それ以降は口頭ベースでやり取りしましょう。

サンプルコード

第8章のソースコードとテストコードを抜いたものを以下に置いています。これをベースに演習を進めてください。

https://github.com/yohei1126/embeddedtdd/zipball/chapter8_init

コードの書き方の細部が分からない場合は、公式サイトのソースコードを参照してください。こちらは全てのコードが入っています。

http://pragprog.com/titles/jgade/source_code

サンプルコードの動作確認

解凍したら make all してください。エラーなしにビルドが通ればOKです。

テストリストの確認

p.130のFigure 8.1 Light Scheduler Test Listを参照してください。本章ではこのテストを一通り消化します。

この時点では全て内容を把握する必要はありません。テストが1個終われば、確認すれば良いでしょう。

テスト対象の設計の確認

p.134のFigure 8.4 Light Scheduler Unit Test Structureを参照。テスト対象のLightSchedulerは、LightControllerとTimerServiceのインタフェースにのみ依存します。テスト中は、LightControllerとTimerServiceをTestDoubleに置き換えることで、テスト可能にします。

p.136 LightControllerSpyのCreateのテスト

p.134~135のテストは途中で中断します。最初はp.136のLightControllerSpyのCreateのテストから始めた方が良いです。

LightControllerSpyTest.cppのテストコードを記述

まずはp.136のテストコードを一通り書いてみましょう。テストコードの配置場所は以下になります。
    tests\HomeAutomation\LightContollerSpyTest.cpp

CppUTestでテストする際に以下は必須です。

  • TestHarness.hのインクルード
  • TEST_GROUPの宣言

また、C++のテスティングフレームワークであるCppUTestを使って、C言語のコードをテストするため、C言語のヘッダファイルをインクルードする際はextern宣言します。

ここで、テストコードを一通り書いた段階でまだプロトタイプ宣言がないため、コンパイルエラーが起きます。

LightControllerのプロトタイプ宣言を実装

コンパイルエラーを解消するため、LightControllerのプロトタイプ宣言を記述します。LightControllerはテスト対象なので、ヘッダファイルの場所はinclude以下になります。
   include\HomeAutomation\LightContoller.h

LightControllerSpyのプロトタイプ宣言を実装

コンパイルエラーを解消するため、LightControllerSpyのプロトタイプ宣言を記述します。
LightControllerSpyはTestDoubleなので、ヘッダファイルの場所はtests以下になります。
   tests\HomeAutomation\LightContollerSpy.h

ヘッダファイルの定義はp.137を参照してください。ここで定義されているenumはテストでのみ使うため、ここで定義しているそうです。

LightControllerの関数定義を実装

リンクエラーを解消するため、テストコードで使われているLightControllerの関数2つを実装します。
ここでは空実装で構いません。

LightControllerSpyの関数定義を仮実装

リンクエラーを解消するため、テストコードで使われているLightControllerSpyの関数2つを実装します。ここではテストが動いていることを確認するため、わざと明らかに違う値を返します。
例)1000

テストすると、失敗します。

LightControllerSpyの関数定義を本実装

テストを通すための最小限の実装を行います。テストの期待値をそのまま返せばOKです。


p.137 LightControllerSpyのRememberTheLastLightIdControlledのテスト

テストコードを記述

まずはp.137の最初のテストコードを記述します。プロトタイプ宣言がない関数があるのでコンパイルエラーが出ます。

LightControllerのプロトタイプ宣言を実装

コンパイルエラーを取り除くため、LightController.hにLight_Controller_Onを記述します。

LightControllerSpyの関数定義を仮実装

リンクエラーを取り除くため、LightControllerSpy.cにLight_Controller_Onを記述します。
※TestDoubleで置き換えるため、tests側に実装しています。

ここでビルドするとテストに失敗します。

LightControllerSpyの関数定義を本実装

テストを通すために最小限の実装を行います。
p.137~p.138にかけてあるとおり、IDと状態を記憶する処理を実装します。

---

以上で最初のとっかかり部分はできました。
サポートもしまずが、後はなるべく自力でがんばってください。

Dual Target Testingで組み込みソフトウェアの品質を早期に作りこむ

4/22(日)に「Test Driven Development for Embedded C 読書会 第3回」を開催しました。当日のやり取りはTogetterにまとめていますので、そちらもご覧ください。

この日は以下の2章をやりました。
  • 第5章「Embedded TDD Strategy」
    • 組み込みシステム開発でTDDをやる上で重要なDual Target Testingについて紹介されています。
  • 第6章「Yeah, but...」
    • TDDを実開発に導入する上でよく聞かれる反対意見とその意見に対する反論が書かれています。
本エントリでは第5章の内容をご紹介します。


ハードウェアとソフトウェアの並行開発が組み込みシステム開発を難しくしている

組み込みシステム開発では、ハードウェアとソフトウェア開発が並行で走ることがよくあります。

以前、「組み込み開発のリスクである「ソフトウェアとハードウェアの並行開発」の解決方法の一つが組み込みTDDじゃないだろうか?」というエントリでもそのことについて書きましたが、この並行開発が組み込みシステム開発が困難になる理由だと私は考えています。それはソフトウェアとハードウェアの両方にバグが混入された状態で開発が進むため、原因の切り分けが非常に分かりづらいバグが発生することがよくあるからです。

並行開発について少し例を挙げてみましょう。私が経験した組み込みシステム開発では、以下のようにいくつかイテレーション(フェーズとも言う)を区切って、イテレーションごとに機能のスコープを切り、イテレーションの最後にハードウェアとソフトウェアを結合して、品質評価をやるというスタイルを取っていました。こういうプロトタイピングを繰り返し、だんだん仕様を固めていくスタイルはよくあるのではないでしょうか?


このように繰り返しプロトタイピングしながら進めていくスタイルでは、
  • 開発序盤にそもそも次期製品のハードウェアがない。
    • たいていの場合は、チップ周りを担当する人には本当に最小限の回路が乗っているハードが提供されて、OSの立ち上がりの確認をしたりする。
    • もっとユーザ寄りの機能を担当する人は、前製品のハードウェアが提供され、全製品と重複している箇所の動作確認からはじめたりする。
  • ソフトウェアとハードウェア双方にバグがある。
という状態で進みます。

そして、イテレーションの最後に結合してリリースした後の品質評価が大量のバグが上がってくると、まずはバグの原因はハードウェアなのかソフトウェアなのかの切り分けから始まります。双方の品質が低い状態なので原因の切り分けははっきり言うと難しいです。そのため、ソフトウェア技術者にとってはソフトウェアにバグはないと確認し、自信を持って主張するためのエヴィデンスの確保に大量の時間を取られてしまいます。

はっきり言って、この時間は不毛だし、生産的ではないでしょう。この不毛な時間を少しでもなくし、より生産性の高い開発を実現するために、組み込みソフトウェアエンジニアは日夜、ハードウェアが届く前にソフトウェアの品質を作りこむことに力を注がなくてはいけません。


Dual Target Testingでソフトウェアの品質を早期に作りこむ

ハードウェアが届く前にソフトウェアの品質を作りこむにはどうしたらいいんでしょうか?書籍で推奨されているのがDual Target Testing、つまりターゲット・ハードウェアとPC上の両方でテストできるようにすることです。

書籍では、以下のプロセスでテスト駆動な開発をしろと解説しています。



特徴は以下。
  • 最初の段階で、徹底的にPC上で単体テストとリファクタリングをして、単体レベルのソフトウェアの品質を確保してから評価ボードに持っていく。
    • デバッグ環境の貧弱な評価ボード上で単体レベルのロジックのミスを見つけるような、不毛な作業がなくなります。
  • PC上でターゲット用のコンパイルを行ったり、評価ボードやターゲットハードウェア上でも単体テストを動かすことで、ホスト環境とターゲット環境の違いによるバグを早期に見つける。
  • ターゲットハードウェアに対する受け入れテストも用意する。
ここでやっているのはあくまでも単体テストなので、単体テストのスコープの検証しかできませんが、評価ボードに持っていくまでに品質を確保できているので、まず開発の立ち上がりがスムーズに行くと考えられます。

また、PC、評価ボード、ターゲット・ハードウェアの3つに対して単体テストが自動的にできるため、結合した上で単体レベルの動作確認などが不要になります。結合した時に振る舞い、あるいはマルチタスクの微妙なタイミングによリ発生するバグなど、もっと複雑な不具合の究明に力を注ぐことができるはずです。


Dual Target Testingをやるには地道に単体テストを積み上げる必要がある

ただし、自動テストをやるには単体テストが用意されている必要があります。これは、いきなり評価ボードに持っていって実機デバッグをやるより、はっきり言って時間のかかる作業だと思います。

単体テストを作らずに評価ボードで実機テストを繰り返すスタイルは、直近で見ると一見手間がかからなくて楽な方法です。しかし、機能を追加するとテスト作業が全てが手作業になり、繰り返し単体レベルの動作を保証することはできません。また、途中で混入したデバッグ作業に時間を浪費し、生産性の向上は見込めません。

Dual Target Testing用に単体テストを積み上げていくスタイルは、直近で見ると時間がかかり、進捗は遅くなりますが、開発を進むにつれて混入したバグを即座に発見することができるため、デバッグ時間の節約にもなり、より難易度の高い不具合の究明に時間をさけるようになります。

この開発時間についてどう捉えるかは6章で扱っているので、そちらで紹介したいと思います。

どうやってDual Target Testingをやるの?

具体的なテクニックは第7章「Introducing Test Doubles」以降で紹介されています。次回の読書会で扱う予定。その際にまたブログで紹介したいと思います。

2012/04/14

#coderetreat in Tokyo 2nd に参加してきた

Coderetreat in Tokyo 2ndに参加してきました。

Coderetreatとはイベント告知のページによると以下のようなイベントです。
Coderetreat は、プログラマのための練習、学習のためのイベントです。同じ課題をペアプロで実装しながら、プログラミング、設計、テストなどの技術を学びます。
特徴を挙げると・・・
  • 全員がライフゲームという題材に取り組む。
  • ペアプログラミングをしながら実装する。
  • 開発45分で振り返り15分で1セッション。
  • セッションのたびにペアを入れ替える。
  • 毎回コードを捨てて、一から作り直すという作業を繰り返す。
  • 言語は固定せずにいろんな言語で実装する。
    • 実際、私はRuby/C/Javaで実装しました。
  • セッションのたびに追加課題が与えられる。
    • 配列を使うなとか、if文を使うなとか、セッター・ゲッターは使うなとか。
このイベントはかなり斬新で、すごい楽しかったです。

これをやることによって何が起こるかというと
  • ペア作業を繰り返すことで、いろんな人の設計アイディアが合成されて、セッションを進むごとに設計が洗練されていきます。
    • 実際、最初のライフゲームのデータ構造は地味な2次元配列でしたが、途中からクラスベースで多次元に対応できるデータ構造になっていきました。
  • 普段使わない言語で実装することで、違った視点を得られたり、違う言語ならではの体験ができます。
    • 特に、私は組み込みCでのTDDに取り組んでいるので、Rubyでのコードの記述量の少なさ、本質的なコードに集中できることの生産性はすごいうれしいと分かりました。
    • C言語の場合、言語的な制約をカバーする要素が多く、Rubyのような生産性はなかなか出せません。
    • ただし、C言語でも(Rubyには及ばないまでも)早いサイクルでTDDを回すことはできますが。
  • 実装の制約をかすことで、よりオブジェクト指向らしいきれいな設計に収束していきます。
今回のイベントに参加したおかげで、ペアプロ、TDD、オブジェクト指向設計の面白さや大切さを再発見しました。

今、組み込みCのTDDの読書会をやっていますが、区切りの良いタイミングで、今回のイベントのように演習中心でペアプロをする会をやれたらいいなという思いが強まりました。その時は、組み込みらしく実機も用意して、dual targeting testを実践すると。

最後になりましたが、場所・食事・おやつのスポンサーのグロースエクスパートナーズ株式会社、株式会社 情報システム総研-、株式会社 スペイシーズの皆様に感謝したいと思います。素晴らしいイベントをやっていただいてありがとうございました!