開発現場で保守性の高いTDD/BDDを実現するための3つのポイント――テストレベル/網羅性とは:いまさら聞けないTDD/BDD超入門(4)(2/3 ページ)
開発現場でTDD/BDDを導入するためのポイントを大きく三つに分けて解説。テストレベルや網羅性、サイクルタイムについても紹介します。
TDDで保守性の高い振る舞いにするポイント
新しい振る舞いを追加するときには必ずテストコードから書きますが、現状のテストコードが振る舞いを十分に表現しているか判断が難しい場合も少なくありません。これらが決まっていないと、後から見たときに「どれが重要なテストコードか分からない」「いくつか記述されている振る舞いの違いが分からない」など、保守性の低いコードが量産されてしまいます。以降では、いつ、どの程度記述すべきかについてポイントを紹介します。
保守する人と保守期間
ポイントを紹介する前に、振る舞いの表現方法(ツール)を選択する際の基準に触れておきます。TDD/BDDにおいては少なくとも次の2点が挙げられます。
- 誰がその振る舞いのユーザーであるか
- その振る舞いが保守される期間はどの程度か
TDDは実装対象を利用するユーザーの立場でテストコードを記述すべきですから、最良のテストコードの一形態として、ユーザー自身が書くという方法があります。ユーザーが書かないとしても、そのユーザーにとって読みやすくあるべきでしょう。
また、振る舞いが保守される期間が短期間のものはより変更しやすい表現方法で書かれている方がよいですし、長期間のものは変更しづらくとも豊かな表現で情報量が多い方がよいです。
テストレベルとテスティングフレームワーク
では、上記の基準をどのような切り口で決めればよいでしょうか。ここではBeizerのテストレベルで分類します。一般的に、それらに対してどのようなテスティングフレームワークを採用しているかは次の通りです。
- ユニットテスト:xUnit、xSpec
- コンポーネントテスト:xUnit、JBehave、xSpec
- 統合テスト:JBehave、Cucumber、Turnip
- システムテスト:JBehave、Cucumber、Turnip
よりエンドユーザーの要求に近いレベルのテストでは、より自然言語として書きやすいテスティングフレームワークを使用します。これは先の「誰がその振る舞いのユーザーであるか」に関わっているためです。
また、要求に近いレベルのものほど実装よりは保守期間が長い傾向があります。例えば、一つの要求を実装するのに1日かかったとしましょう。この場合、要求は最低でも1日は変わっていなかったが、実装は数分単位で変わっていくことがあります。そのため、より情報量が多い表現が好ましいです。自然言語には、その人達が使う言葉や状況が多く含まれます。
ただし、絶対にこれに従う必要があるわけではありません。あくまで傾向の話です。例えば「xUnit」「xSpec」などの方が「Cucumber」よりツールとしての柔軟性が高いため、それらで統合テストを実装することは現実的な選択肢です。
では、本題の振る舞いを記述するタイミングや選択方法についてポイントを紹介します。
【1】一度に記述する量や難易度はサイクルタイムで決める
振る舞いを記述したはいいものの、実現が難しく、簡易な実装をするために1時間かかってしまうことがあると思います。一度に複数のパターンを書いたときにも同様のことが起きがちです。できるだけ短い時間で達成する方が「何が開発できていて」「何が開発できていないのか」がハッキリしやすいため、「できるだけ短い時間で実装できる量や難易度の振る舞いを書いて実装し、リファクタリングする」というTDDのサイクルを回す方がよいです。
TDDは短いサイクルを持つプロセスであるからこそ、着実な成果と「無駄なものを作り過ぎない」という効果がありますが、長時間かかってしまっていてはそれらの効果を得ることができません。
しかし、テストレベルが高いものになってくると、その振る舞いに合意する人が常に開発チームと一緒になって書いてくれるとは限りません。例えば、インテグレーションレベルのテストを合意する人は2週間に一度しかミーティングを行えないような状況が考えられます。このような場合は、2週間で実装できる量と難易度の振る舞いを記述するようにします。
ただし、表現すべき振る舞い全てをあらかじめ設計しておくことは難しいでしょう。実装してから判明する振る舞いや、コンポーネントテストとのバランスによって変化する振る舞いがあるからです。
一度に書く量が多い場合に重要なのは、その振る舞いを合意する人が「振る舞いの優先順位がついている」ことです。それは、いわゆる「テストメソッド」単位でもそうですし、パラメタライズされたテストメソッドであれば、そのパラメータの優先順位についてもです。そうしておかなければ、実装がつまったときや、他の実装の変更によってテストが失敗した場合に、システムとして何を保証すべきかの判断に時間がかかりやすくなってしまいます。
補足「サイクルタイムとは」
読者の皆さんは「サイクルタイム」という言葉を聞くのは初めてかもしれませんが、筆者が日本のTDDコミュニティの幾人かとの議論するうちにTDDの考え方を表現する際に、サイクルタイムという言葉が使いやすいことに気付いたので、この連載ではサイクルタイムという言葉で説明します。
サイクルタイムはRED、GREEN、REFACTORが一巡する時間と定義します。
例えば、前回の記事でもあったようにユニットテストのTDDでは数十秒から数十分程度で1サイクルの目安とされていますし、インテグレーションテストが関わってくることが多いATDDでは1日から1イテレーション(例えば1週間)が目安とされています。
筆者を含めてTDDに慣れてくると、対象の開発環境(プログラミング言語、各種フレームワーク、ライブラリ、エディター、IDEなど)に慣れている状態では、ユニットテストにおけるサイクルタイムは1分以内であることが多いです。
ユニットテストレベルの振る舞いをTDDするのに1日では長いですし、インテグレーションテストレベルの振る舞いをTDDするのに1カ月では長いです。それくらいの期間を使ってしまうと、自分がいまどこまでできているのかが分からなくなってしまったり、他者に協力を仰ぐときに何ができていて何ができていないのかを説明するためにもまた時間がかかってしまったりすることもあるでしょう。
一方で、テスト失敗からテスト成功までの時間の意味(つまりリファクタリングが存在しないかのように扱う意味)で「テストサイクル」とも呼ばれています。テストレベルが高い場合のサイクルタイムは、実際にはこちらの意味になることが多いです。
これにはさまざまな理由がありますが、その中でも大きな理由の一つとして、TDDでは継続的にリファクタリングをすることが多くなることが挙げられます。もちろん、事前設計があまりにも行われていない場合や、システム要件があまりにも変わってしまう場合に、テストレベルが高い状態でのリファクタリング(往々にして「リストラクチャリング」)が発生することは十分にあり得ます。
【2】各テストレベルで高いコードカバレッジを保つようにする
実行が自動化されている全てのテストレベルでコードカバレッジツールを利用して、コードカバレッジを計測しましょう。例えば、コンポーネントテスト、インテグレーションテストのどちらにおいてもC1カバレッジを80%に保つようにします。いまあるシステムに対して各ユーザーにとって常に十分に振る舞いが説明されていることが重要です。
筆者はコードカバレッジを利用することを強く勧めるつもりはありませんが、簡易にプロダクトの状態を知れるツールとしては魅力があるのも確かであり、設計や保守性、あるいは変更に対応できる時間的余裕や品質に自信があれば必要がないとは思いますが、少なくとも手探りでTDDやさまざまなテストレベルのテスト実行自動化を進めていくなら、有用なツールでしょう。
TDDを実践していると最も振る舞いを書きやすいツールを利用したテストレベルのコードカバレッジが集中して上がることがありますが、さまざまなレベルでの開発があるなら、それらに対応するさまざまなレベルの振る舞いであるテストコードがある方が、より精緻でフィードバックの多いTDDを実践できます。
逆に言えば、コンパイラーによってチェックが入る部分やコード生成で済む部分に関しては開発をしていないので、それらに対応する振る舞いを書く必要はほとんどの場合ありません。その場合には、例えばテストレベルや対象によってコードカバレッジが変化するでしょう。
Copyright © ITmedia, Inc. All Rights Reserved.