テストサイクルの選択肢を広げるということ

 先日DevLove主催の「世界のすべてをテストせよ。 〜Make the world Green by Test ! 〜」にて、「組込みでデベロッパーテストを導入するための考え方」と題してテストに関するライトニングトークをさせていただいた(参加報告については、後ほど行う予定)。

http://infog.0ch.biz/download/DevLoveTestLT.pdf

 今回は時間不足で十分に説明できなかった、「テストサイクルの選択肢を広げる」ということについて、補足をしようと思う。

自動化しないテストのデメリット

 とりあえず発表の前半では、「自動化した単体テスト」を使用しないデベロッパーテストについて触れた。例としては、以下を挙げている。

  • UI部をきちんとしたテスト設計とテスト手順書を使って、手動でテストする方法
  • 制御や画像処理を、動的解析や動的解析&モデルベーステストを使ってテストする方法

 ただこれに関しては、当然として、口で言うほど簡単にはいかない。当たり前のことだけれど、そこでは大きく以下の3つの問題が出てくる。

  • まず第一に、テストの設計・実装に手間がかかる。手動テストでは、特定のコードの挙動を間接的にチェックすることになるため、テストの設計・実装時に、コードをホワイトボックス的に慎重に分析することになる。具体的には、入出力やフロー・型をよく分析して、必要かつ最適化された入力の組み合わせを用意しなければならない。さらにこうした作業は、テストコードのように蓄積的に網羅性や信頼性を高めていく方法が取りにくい点で、ある程度まとめて行う必要がある。結果、設計・実装自体に大きな手間がかかることになる。
  • 第二に、テスト対象にそれなりのテスタビリティを要求する。例えばホワイトボックス的にコードを分析して入力を絞りこむアプローチでも、テスト対象が他のコードとうまく分離されていなければ、テストできない領域が発生したり、入力組み合わせが爆発したりと、色々と不具合が出てくる。またテスト対象のテスタビリティの低さから、テスト用のプロトタイプやフレームワークを用意しなければならなくなる場合も多い。即ち、テスタビリティの悪さが、テストの手間を大きく増大させる。
  • そして第三に、ある程度の資産の蓄積を必要とする。例えばいきなりテスト仕様書を書けといわれても、慣れないうちはかなり無理があると感じる。仕様書の設計や実装をある程度円滑にするためには、そういったドキュメントのフォーマットを定めたり、仕様書の作成手順書を示したりと、標準化やマニュアル化の整備を進めなければならない。また手動テストでは、手動・主観を補うものとしてデバッガやカバレッジツール、オシロスコープといったツールや測定装置を必要とすることが多いという背景もある。結果、円滑にテストを実施するためには、そうした環境の整備を進める必要がある。

 こうした手間は、デベロッパーテストを実施する際の大きな制約になる。というのも、そもそもデベロッパーテストというのは、テストコードによる自動化でテストが軽快にできるようになったため、許容できるリターンが広がり(細かいリターンでも総合的にプラスにできるようになり)、対象を拡大した、という経緯があるためだ。そこで自動化を捨てるということは、これまで見出されてきたデベロッパーテストの利益の多くを補うだけの、別のリターンを確保しなければならないことを意味する。
 少なくとも大前提として、目的に対してテストのメリットがテストのデメリットを上回らなければデベロッパーテストは実施する意味がない、という原則を忘れてはならない。

テストサイクルの選択肢を広げるというアプローチ

 こうした問題の前で重要なものの1つが、許容できるテストサイクル(テストしてからそのフィードバックを得られるまでの手間)の選択肢を広げる、というアプローチだと、自分は考えている。それは具体的にいうと、以下のアプローチを示す。

規模を広げる

 例えば10ステップ程度の関数を実装する作業を、デベロッパーテストで効率化せよといわれても難しいものがある。テストなしに一気に書き上げた方が、効率がよくなる場合が少なくないと思う。
 しかしこれが100ステップ程度になると、見通しが悪くなる分、バグチェックなどの形で、テストによる効率化を活かせる可能性が出てくる。さらに数千行ステップになれば、もうTDDといった本格的なデベロッパーテスト手法で効率化が計れるようになるだろう。
 要は何を述べたいかというと、規模が大きくなれば、「見通しが悪くなる」「コードの複雑度や潜在リスクが増大する」「所要時間が大きくなる」といった要因から、より手間がかかるテスト手法が使えるようになる、ということだ。これは即ち、非自動化テストといったテストサイクルの大きなテストは、実装作業の一部分の範囲では導入が難しくても、開発工程やマネジメントレベルになれば、導入できる余地が増えてくることを意味する。

実施を継続する

 前述したが、非自動化テストを円滑に進めるためには、環境の整備が必要になる。例えばドキュメントの標準化や、テスタビリティを向上させるためのコーディング規約の導入、あるいはツールや測定装置の整備などが必要となる。
 こうした環境整備で重要なのが、テストの実施を継続することだと考えている。例えば継続的に実施していけば、環境や体制、人材の整備がかなりやりやすくなる。これらはテストの手間を削減するため、本来テストサイクルが大きなテスト手法も、より導入しやすいものになってくる。

テストの再利用性を高める

 デベロッパーテストでは、既存のテストが無駄になることが多い。例えば、上位の単体テストを利用してダイナミックにリファクタリングを行った際は、下位のメソッドレベルのテストコードがまとめて無駄になる、といった場面は自分でもよく遭遇する。Mockやスタブ、テストドライバなどテストを補助するコードも、テストのレベルや対象が変わるにつれて、無駄なコードと化すことが多い。
 ただこういった問題は、自動化されていればだが、テストコードの修正・管理が軽快だという点で、致命的な問題となっていない。例えば下位のテストが無駄になりそうな雰囲気が出てきたら、より上位のテストにまとめるといったことも気軽にできるし、手間がかかってない分、テストコードを捨てるのもそんなにクリティカルな問題にならない。
 しかしこれが非自動化テストになると、テストの設計・実装の手間、要求されるフレームワークやプロトタイプの規模から、深刻な問題になることが多い。例えば修正にしても、コードを分析しなおしてテストを実装し、それを仕様書としてまとめる、という手間がかかる。また新規で作る際はそれ以上の手間がかかるため、テスト使い捨てのダメージも大きい。
 そのため、非自動化テストを運用するに当たっては、こうした問題の改善は避けてはならないと感じる。言い換えれば、テストの再利用性を高める努力を、非自動化テストを運用する際は捨ててはならない、と感じる。

開発プロセスやプロジェクトマネジメントを志向するということ

 こうした視点を突き詰めるための手段の1つが、開発プロセスやプロジェクトマネジメントのためのテストを志向することだと考えている。具体的に言うと、例えばイテレーション開発での反復のためのテストや、プロトタイピングのテスト、QAテスティングで定義される単体テストや単機能テストを、開発プロセスとしてデベロッパーが運用することを意味する。

 これも口で言うほど簡単にはいかないが、実績や方法論がすでに蓄積されているため、決して非現実的な対策でもない。少なくとも、こういった開発プロセスやプロジェクトマネジメントを支えるためのテストを運用できる体制ができれば、「規模を広げる」「実施を継続する」「テストの再利用性を高める」の3つを促進できる。そして、テストのコストを下げると共に、テストのリターンを大きくすることができる。結果、許容できるテストサイクルの選択肢が拡大し、自動化や単体テスティングフレームワークに頼らないデベロッパーテストも、ある程度許容されるようになる。

 もちろんこうした開発プロセスやプロジェクトマネジメントから外れたテストを実施するのも問題ない。例えば、テストコードが書けないコードを書けるコードにリファクタリングするための手動テストなんかは、小規模で使い捨てにされる場合が多いものの、実施しても問題ない。あくまで重要なのは、そうしたテストを実施する一方で、開発プロセスとしてのテストを運用するということだ。プロセスとしてテストサイクルの大きなテストを継続して実施できれば、より多彩なテスト手法が使えるようになるという点で、雑多なテストにも良いフィードバックがかかってくる。

 こういった考え方は、一個人の裁量を越える点で実施が非常に大変なのは確かだが、組み込みといった制約の多い開発環境ならば、十分割に合うものだと思う。