開発現場でTDD/BDDを導入するためのポイントを大きく三つに分けて解説。テストレベルや網羅性、サイクルタイムについても紹介します。
前回の『TDD/BDDにおける「振る舞い』の意味するところとは何なのか」までで述べたような、TDD/BDDを導入するときには、現場で「で、今までやってきた単体テストと結合テストって、どうやってこれに組み込めばいいんだっけ?」「網羅的なテストをどうやって書けばいいんだろうか?」「テストを先に書くだけくらいにしか違いがないのではないだろうか?」などの疑問が出てきます。
今回は、これらの導入時の疑問を解決するようなパターンを紹介します。まずは説明のためにいくつかの言葉の定義を紹介してから、どういったことで保守性の高いTDD/BDDを実現できるかを紹介します。
大まかに言えば、「テストレベル」とはテスト対象の大きさやテスト対象の責任範囲を基準とするテストの分類です。
以下に3つほどテストレベル定義の例を示します。それぞれでテストレベルが低いものから書き始めてあります。
テストレベルの定義には多くの方法がありますが、筆者がよく使うのはBoris Beizer氏の定義です。Beizer氏の定義による説明がプログラマーにとってより具体的な例示をしているためです。Beizer氏は『ソフトウェアテスト技法』(日経BP刊)という書籍の中で次のように定義しています。
1ファイル内で動作が可能なテスト。ほとんどの場合はモックが必要です。
あるユニットが動作するのに必要なユニットを含めたテストです。
例えば、「Foo」機能のエントリである「Aaa」クラスのあるメソッドをテストするとしましょう。そのメソッドでは「Bbb」クラスを参照しており、「Ccc」クラスをフィールドに持っていたら、「Bbb」クラスまで含めたものも、「Ccc」クラスまで含めたものもコンポーネントテストになります。つまり、ある機能のコンポーネントテストを達成するためには、各ユニットの再帰的な定義が必要になります。
コンポーネント間のテストです。先のコンポーネントテストでは、「Foo」機能のために「Aaa」「Bbb」「Ccc」といったクラスの関連をテストしたわけですが、「Bar」機能のための「Xxx」「Yyy」「Zzz」クラスは別のコンポーネントと考えることになります。この場合、直交しているかもしれませんし、別のリソース部分で何か関連しているかもしれません。この連携をテストします。
システム全体を動作させるテストです。コンポーネントテスト、統合テストでは検出されないようなバグを検出させます。自然と「JSTQB(Japan Software Testing Qualifications Board)」が規定しているステージング環境を想定した実際のシナリオに基づくテストが必要になります。
書籍『実践テスト駆動開発』(翔泳社刊:Growing Object-Oriented Software Guided by Tests)(以下、GOOS)では次のように定義しています。
「オブジェクトは正しく振る舞っているか?」「また、オブジェクトは扱いやすいか?」をテストします。
「私たちが変更できないコードに対して、書いたコードが機能するか?」をテストします。
「システム全体が機能するか?」をテストします。
また、筆者が今までいくつかの日本の仕事で見てきた中では次のような分類もありました。
ユーザーから見える単一の機能もしくは1画面に対するテストです。
社内で完結する機能を組み合わせて、ユースケースに対するテストです。
社外の組織が提供している機能を組み合わせて、ユースケースに対するテストです。
プロダクトのライフサイクルで起こり得る業務シナリオや性能に対するテストです。
顧客が行うテストです。
このようにテストレベルはさまざまな分類がありますが、GOOSでも述べられているように、このテストレベルならこの技術がよいといった話はチームやソリューションによって大きく異なるため、テストレベルのみから具体的な解決方法へアプローチする方法は限定的にしかできないことが多いです。
また、テストレベルが高いものほどアプリケーションドメイン(問題領域)、テストレベルが低いものほどソリューションドメイン(解決領域)に対するテストを行っているともいえます。
アプリケーションドメインとは「あるソフトウェアによって変化させたいことそのもの」のことです。いわゆるソフトウェア開発における要求や要求仕様として表現されるようなものをイメージしてください。
ソリューションドメインとは「あるソフトウェアで実現する方法そのもの」のことです。いわゆるソフトウェア開発における基本設計や外部設計として表現されるようなものをイメージしてください。
詳細はWebサイト「オブジェクトの広場」で金澤典子氏が公開している『マルチパラダイムデザイン - 序論 -』や、書籍「実践プログラミングDSL」(翔泳社刊)を参照してください。
バグを出してしまったときに、「テストをしたのに『テストケース漏れ』『組み合わせ漏れ』を発見できず、バグを検出できなかった」などのような表現を聞きます。テストにおいて、網羅は「網羅対象」「網羅基準」という2つの言葉によって定義されます。
そのテストにおいて『“何を”網羅しようとしているか』の“何を”に当たるもののことです。例えば、ある関数において引数を網羅することがあるかもしれません。もしくは返り値を網羅することがあるかもしれません。
そのテストにおいて『何を“どのように”網羅しようとしているか』の“どのように”に当たるもののことです。全てを網羅することは非常に難しいので、多くの場合はなんらかの網羅基準をテスト技法ごとに選択し、網羅対象をいくつかに絞ります。“全て”ではなく“それら”を選んだ基準を示すイメージです。
では、割り算の関数において網羅対象と網羅基準が何かを例に示します。
Groovyでは記号「/」が割り算の動作をします。今回は網羅対象を「被除数(割られる数)」と「除数(割る数)」と「商(割り算の結果)」とします。網羅基準は同値分割の技法に基づき「除数と被除数の絶対値に関わらない商」「商の正負」「いくつかの代表値」を含むようにします。
テスティングフレームワークの都合上、除数が0の場合を別関数にしています。
class WhenCoveringDivision extends Specification { def "should cover division without error"() { expect: dividend / divisor == quotient where: dividend | divisor || quotient 6 | 1 || 6 6 | 2 || 3 6 | 6 || 1 10 | -1 || -10 -10 | 2 || -5 -10 | -10 || 1 0 | 1 || 0 0 | -1 || 0 } def "should cover division error"() { when: dividend / divisor then: thrown(ArithmeticException) where: dividend | divisor 1 | 0 0 | 0 -1 | 0 } }
上記例は一関数であり、副作用がなく標準APIなので、詳細なテストケースは省略してあります。筆者が仮に割り算関数を自作するなら、「極大値」「極小値」「null」など言語やツールに応じてさまざまなパターンを追加もしくは減らします。
Copyright © ITmedia, Inc. All Rights Reserved.