開発現場で保守性の高いTDD/BDDを実現するための3つのポイント――テストレベル/網羅性とは:いまさら聞けないTDD/BDD超入門(4)(3/3 ページ)
開発現場でTDD/BDDを導入するためのポイントを大きく三つに分けて解説。テストレベルや網羅性、サイクルタイムについても紹介します。
【3】振る舞いの単位を統一する
各振る舞いのくくり方を統一することで理解しやすくなり、プロダクトへの変更やバグが発生した場合の影響や理由を分析しやすくなります。例えば、次のようなものが挙げられます。
- 振る舞いの組み合わせ方
- 振る舞いの入力
- 振る舞いの結果
- 振る舞いを記述する動機
- 振る舞いの重要度
分類する方法は、ディレクトリ、ファイル、メソッド、アノテーションなどによるタグ、クラスやメソッドでのネストなどがあります。どれが最適であるかは利用するフレームワークによって異なります。これらをテストレベルや網羅基準によって使い分けることで見通しの良いテストコードになります。
- 振る舞いの組み合わせ方
「一つのシナリオにおいて何をどの順番で行うか」によって分類する場合です。このパターンを採用する理由は「意味のない名前を付けてしまう振る舞いを減らす」ことや「重複して変更箇所が大量になるテストコードを減らす」という点です。
おそらく、多くのテストメソッドは書きやすさからこの分類を採用していることが多く、重複したテストメソッドを共通化する場合や、パラメタライズテストを行う場合は「振る舞いの組み合わせ方」でくくっていることが多いでしょう。
例えば、テストコードにおけるプロダクトコードのメソッドや関数をどのように実行するかです。プロダクトコードで言えば「メソッドを共通化しよう」と言われるような状況がよくこれに当てはまります。
- 振る舞いの入力
「振る舞いを構成する入力や各振る舞い自身」によって分類する場合です。このパターンを採用する理由は「何が変わると振る舞いが変わるのか、変わらないのか」を明確にするという点です。
振る舞いを実現する入力値によって分類する場合の代表例としては、入力となる引数や設定値などを網羅対象として見なし、パラメタライズテストを構築する場合などが該当します。振る舞い自身によって分類する場合の代表例としては、RSpecなどで使えるContextのネストなどはこれに該当します。各シナリオで共通している部分がインデントで分かりやすい見た目になります。
また、前回までのBDDの説明において記述したように、Given/When/Thenは状態遷移と見なすことができるので、振る舞い自身によって分類する場合を状態遷移図のパスを書いていると見なして記述することもできます。この場合には、状態遷移のパスがどの程度網羅されているかの判断材料の1つとして各インデントと、インデントのネスト数が利用できます。
テスティングフレームワークや言語によっては、入力によって分類することが難しい場合もあります。例えば、例外発生に関するテストの中には、別のテストメソッドでくくらなければいけないものもあります。
また、パラメタライズテストなどを活用していく中で、後述する振る舞いの出力と併せて全てを網羅するようなテストメソッドを書いていくことは可能です。振る舞いが小さいものであれば、それは十分に可能で、理解性が向上するのでぜひ試すべきです。しかし、振る舞いが大きくなるほど、現状のテスティングフレームワークでは非常に読み書きしにくいことがほとんどです。複雑で読みにくいシナリオを書くことになり、シナリオにバグを埋め込んでしまう可能性が高くなります。
テスティングフレームワークやライブラリを自作やカスタマイズしない状況では、何らかの形で読み書きしやすい方針を採ることで、ある網羅基準を満たしているかが明確には識別しにくくとも、テストコードへのバグ混入を避ける方が重要である場面が多いと筆者は考えています。
- 振る舞いの出力
「振る舞いで起きる変化」によって分類する場合です。このパターンを採用する理由は「どのようなことが実現可能なのか」を明確にするという点です。
一つの方法として、比較的大きな単位であるフィーチャ名やクラス名で表現することがあります。実際にはテスティングフレームワークが粒度を規定していることが多いです。出力でまとめる場合には、同じような出力を得るために同じ対象を使ってもさまざまな方法が存在するケースが多く、それが同一の結果になることを書くためには、いくつかの振る舞いをテストコード側に別途、メソッド抽出のような形で定義する必要があるでしょう。
特にインテグレーションテスト以降のテストレベルではシステムの利用者が増えるほど、別の方法で同じことが実現できていること、もしくはできないことについて確認が取りやすいと、システムの変更に対する判断をしやすくなります。ある機能を変更することによる他への影響が分かりやすいためです。
- 振る舞いを記述する動機
「この振る舞いが必要である理由」によって分類する場合です。このパターンを採用する理由は「保守しなければいけないかどうかを判断しやすくする」「保守しようとしているものが矛盾していないかどうかを判断しやすくする」という点です。
最初に振る舞いを記述するときには、テストレベルによって、つまり対象を使うユーザーごとに「○○を達成するために必要」となります。インテグレーションテストレベルより高い場合には比較的ビジネス寄りの理由になってきますし、コンポーネントテストレベルより低い場合には比較的技術的な理由になってきます。もし、アジャイルでユーザーストーリーを使っている場合には「Why」によって分類するというイメージがほぼ近いです。
また、実装における特定のパターン(バグがありそうなパターン)の振る舞いもありますし、外部的な要因にどこまで影響を受けるのかを明示的にする振る舞いもあります。このようにしておくと、「テストが失敗してしまったが、なぜそれが必要なのか」分からないような「パラメタライズテストにある謎の組み合わせ」で困惑するような事態を防ぎやすくなります。
- 振る舞いの重要度
「その振る舞いを利用するユーザーにおける重要度」で分類する場合です。このパターンを採用する理由は「プロダクトを実装する優先順位を判断しやすくなる」「保守しなければいけないかどうかを判断しやすくなる」「プロダクトのコア部分とそれ以外の違いを意識しやすい」という点です。
ある振る舞いのビジネス的もしくは技術的な重要度が分かることも大切ですし、振る舞いにおける入力値の組み合わせの重要度が分かることも大切な場面が存在します。よくあるのは、特定のフィーチャやメソッドにおいてある入力値の場合には別の結果になったり、外部要因の例外的な振る舞いに対しても安全に振る舞うというケースの重要度を低くしたりすることです。
筆者は、これらについてはアノテーションやパッケージ、プロジェクトを使って分類することが多いです。
例えば、「Must」プロジェクトと「Should」プロジェクトにしておき、ShouldプロジェクトでMustプロジェクトを参照するようにすることで、Mustプロジェクトで定義したユーティリティなどを利用しながらより細かい組み合わせの振る舞いを記述したり、より長い組み合わせを簡単に記述できたりします。
また、こうしておくことで、重要度の低い振る舞いやユーティリティがMustな振る舞いを記述する箇所から除外されるので、プロダクトのコアを理解しやすくなります。
次回は、スタブやモックについて
TDD/BDDを採用すると、たくさんのテストコードを書くことになります。そこで今回は、振る舞いとして書いた上で、それらをどのように自分たちのプロダクトとして保守しやすくし、また、どのようにしてより良いフィードバックを得るかについての下記三つのポイントを紹介しました。これらは前提として、保守する人と保守する期間に応じて表現方法を変える必要があります。
- 記述する量や難易度はサイクルタイムを一つの指標とする
- 各テストレベルでのコードカバレッジの傾向を利用する
- 振る舞いの単位を統一する
プロダクトをどのように知りたいのか、プロダクトからどのようにフィードバックを受けたいのかという視点でTDD/BDDを行うことで、大量のプロダクトコード、テストコードとうまく付き合えるようになり、システムを成長させるスピードをできるだけ維持できます。
次回は、TDD/BDDを行う上で話題になりがちなスタブやモックについて触れる予定です。
著者プロフィール
ソフトウェアテストアーキテクト。ソフトウェアテストを専門にし、テスト戦略やテスト設計に関するモデリングの手法やアジャイルなソフトウェアテストに関する研究と実践を行っています。GroovyやF#などのプログラミング言語を好んでいて、断然IntelliJ IDEA派です。Nagoya.Testing、SCMBootCamp、基礎勉強会などの勉強会を主催しています。
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- テスト自動化のROIを計算してみよう
テスト自動化の導入理由や効果測定をROIという観点で説明できるように、テスト自動化のROIの概念から実際の計算式までを解説する連載。 - テスト自動化の歴史と今後、良い/悪い事例〜システムテスト自動化カンファレンス2013レポート
テスト自動化を開発の“武器”にするための3つのポイントや、“自動化”の良い事例、悪い事例など、テストの現場を「進化させる」知見が多数紹介されたカンファンレンスの模様をレポートする。 - PHP開発者のためのテストのすゝめ(1):ユニットテストはなぜ必要なの?
開発の全工程の中で、あまり人気がないのがテスト工程だ。ソフトウェアの品質を証明するためのテストは、なぜ低く見られてしまうのか - Railsで目指せ、情熱エンジニア(5):なぜリファクタリングは必要なのか?
今回からいよいよコードの話を始めます。もはやRubyの文化の一部だという主張もあるリファクタリング、テストについて、その意義や概要を紹介します - Railsで目指せ、情熱エンジニア(7):Railsの人気テストフレームワーク6選!
前回は具体的なWebアプリを例にして簡単なコードレビューをしました。今回からは、テストを使ったリファクタリングについて解説していきます - Railsで目指せ、情熱エンジニア(8):実例で学ぶRailsアプリのテスト方法
前回はRailsで使われるテストフレームワークをご紹介しました。今回は具体的なWebアプリを例に、簡単なテストを使ったリファクタリングについて解説します - 第1回Androidテスト祭りレポート:Android開発で泣かないための「テスト」の重要性
その自由度の高さや多様性ゆえに、さまざまな課題を抱える、Androidアプリ開発の“テスト”に焦点を当てたイベントの模様を紹介します - UX Clip(28):JavaScriptのテストを開発工数に入れてもらうには?
Webアプリの大規模化とフロントエンド領域の拡大により、JavaScriptのテストの必要性が高まっている。数ある高機能なテストフレームワークをどう使いこなせば、高速かつ高品質な開発が可能になるのだろうか。 - テスト駆動開発はプログラマのストレスを軽減するか?
テスト・ファーストとリファクタリングの技法を組み合わせてプログラム開発を行う「テスト駆動開発」。簡単な例題を試しながら、この新しい開発手法を評価、検証する - 特集:Visual Studio 2008単体テスト機能徹底活用(後編):VS 2008単体テスト機能でテスト駆動開発/NUnitからの移行
VS 2008 Pro版に搭載されている単体テスト機能を使ってテスト・ファーストを実践。NUnitからのテスト移行についても考察。 - IT用語解説系マンガ:食べ超(3):テスト駆動開発で、みずから築いた壁にドーン
テストを書け! 話はそれからだ!