テスト駆動を導入するための3つのポイントテスト駆動開発で行こう!(後編)

前編「テストを金額にするといくら?」ではテストの価値を価格として算出し、その重要性を検証するという思考実験を行いました。その結果、大ざっぱながらも、テストの重要性に少し現実味が持てたのではないでしょうか。後編では、テスト駆動開発をプロジェクトに導入して、運用するための具体的なノウハウを紹介します。

» 2006年05月28日 12時00分 公開
[五十嵐博, 安中伸彦,株式会社シンプレクス・テクノロジー]

テスト駆動開発(TDD)を回すのにもノウハウが要る

 TDDでは、いままでよりユニットテストコード作成にウェイトが置かれるので、ユニットテストコード作成の進ちょくがプロジェクトの進ちょくに与える影響は大きくなります。つまり、ユニットテストコード作成の効率性をより高めていかなくてはならないわけです。

 では、ユニットテストコード作成の効率性を高めるには、具体的にどうすればよいでしょうか? まずは以下の2点を押さえるべきでしょう。

  • EoTの高い設計になっていること
  • テストコードが正確に業務仕様を表していること

 まずは、「EoTの高い設計になっていること」について考えてみましょう。

 EoTの高い設計をするためには、

  • テストしやすいモジュール分割をする
  • テスト時にMockを使用することを考慮して、インターフェイスを作成する
  • 実クラスとMockクラスを容易に変更できるようにする(これは、DI[=Dependency Injection]を利用することで容易になりました)などの技法があります。

 ところが、実際のプロジェクトでは、全員にこういった設計、実装を期待するのは、開発者が多ければ多いほど難しいですよね。筆者らは以下のような方法が有効だと考えています。導入自体、それほど難しくありません。

  • (A)最初に経験豊富な数人のメンバーで代表的なユースケースを設計・実装する。テストコードも十分に実装する
  • (B)そのコードをひな型にして、ほかのメンバーは担当するユースケースを設計・実装する

 “Aでテストコードを十分に実装する”という条件がないなら、この方法を導入しているというプロジェクトは多いと思います。これは、プロジェクト全体の品質・粒度が一定になりやすい有効な方法です(特に開発者が多くなればなるほど)。

 一般的に、Aは本格的な実装開始前に行うことが多く、そのスケジュールは非常にタイトです。結果として、テストコードの作成は行わないか、行っても簡素化されてしまいます。

 しかし、Aで頑張ってテストコードもしっかりと作成すれば……、

  • EoTの高い設計ができる。ほかのメンバーはこれを参照するので、ほかのメンバーの担当部分もEoTの高い設計になる
  • ……そして、テストコードをひな型として使用できるため、プロジェクト全体でテストコードの書き方や粒度が合いやすい

 となり、結果としてBの効率が上がるわけです。Aの開始をいままでより早めるなどスケジュールを調整して、Aでテストコードをしっかりと作成することをお勧めします。

 次に、「テストコードが正確に業務仕様を表していること」について考えてみます。

 『ユニットテストコードを作成したが、業務仕様と比べると過不足・誤りがあったことが、結合テストや総合テストで分かった』。こういったケースはとても多いようです。その影響が1つのテストクラスだけならよいのですが、連鎖して複数のテストコードを修正することになったら厄介です。どうして、そんなことが起こるのでしょう?

 理由は単純です。テストコードの内容がレビューされていないからです。本体のソースコードがテストコードを満たしていることは保証されます。そのテストコードが仕様に合致していることはレビューされたのでしょうか?

 テストケースが押さえるべきところを押さえているか、仕様を誤解していないかなどを厳密にレビューできる人は、プロジェクト内において、少数であるケースが多いと思います。テストケースをきちんとレビューする際には、実は、テストレビュアーがボトルネックになりやすいのです。ということは、テストレビュアーたちには効率的にレビューしてもらうよう、対策を立てないといけません。

 経験的には、各テストの内容を説明したテストケース一覧があると、チェックが効率的に行えるようです。テストケースの管理も容易になります。でも、そういった一覧をソースコードと別に管理するのはできれば避けたいところです。そんなときに、以下のようなアノテーションを使用するのが効果的です。

@TestSpecification(
    name="ログイン処理で、パスワードを間違えたときのテスト",
    description="登録されているパスワードと異なるために、ログイン不可の結果が返る",
    step={"1.ログイン名=「Taro」、パスワード=「xxxxx」がDBに保存されてい
る。",
                        "2.DBデータで、ログイン名=「Taro」のログイン失敗回数=1である。
"},
                        "3.入力値が、ログイン名=「Taro」、パスワード=「yyyyy」である。
"},
    expected={"1.ログイン失敗回数が、2である。",
                        "2.エラーコードErrorID-100がリターンされている。"}
)
public void testLoginFailed() {.....}

※アノテーションの文字数が多いので、意図的に文字のポイントを小さくしています。(編集部)


 TestDocを定義しているソースは次のとおりです。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
    * Testメソッドを説明するアノテーション。
    */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface TestSpecification {
            /**
            /**
            * テスト名を表す。
            */
            String name();
            /**
            * テスト内容の詳細情報を表す。
            */
            String description();
            /**
            * テストのステップを表す。
            */
            String[] step();
            /**
            * テストの予想結果を表す。
            */
            String[] expected();
            }

 * @Documentedが設定されたアノテーションはjavadocの文章化の対象になるので、TestDocには@Documentedアノテーションを付けたいところですが、日本語が文字化けするようなので付けていません。


 AnnotationProcessorインターフェイスなどを使用すれば、アノテーション情報を読み出せます。その情報をエクセルやCSV形式に変換するプログラムを作成して、テストケース一覧表を作成することができます。また、javadocコメントにテストケースを記載する場合、開発者ごとに書く内容が異なりやすいのですが、この方法なら開発者ごとのずれは発生しにくいといった効果も期待できます。

ユーザーI/Fはどうするんだ?

 業務システムにTDDを適用しようとしても、ユーザーI/F回りは適用しづらい領域と一般にいわれています。ユーザーI/F回りはTDDの領域から除外すべきだという意見も多いようです。理由はビジネスロジックと違い、ユーザーI/Fは人間の介在が前提となっているために、テストコードが書きにくいからです。しかし、業務システムにおいては実はこの部分のテストに占める割合は大きいのです。テスト工数が大きいということは、テスト駆動開発がうまく導入できた場合には、その恩恵も大きいということになります。

 もし、ユーザーI/F部分にもテスト駆動開発を導入するとすれば、先に挙げた2つのポイント

・EoTの高い設計になっていること

・テストコードが正確に業務仕様を表していること

に加え、

・テストをしやすくするツールを利用する

を検討しなければなりません。

 いままでユーザーI/Fにはテスト駆動開発が適用しづらいといわれてきている理由の1つに、ちょうどよいツールがあまりなかった、という点があると思います。ユーザーI/Fのテスト自動化ツールとしては一般的にマウス操作の記録・再生方式が主流なのですが、この方式ではそもそもテスト駆動開発が目指している設計文書(実装する前の設計文書)としての役割が担えません。つまり低レベルのマウスの動きやマウスボタンのプッシュ・リリースではなく、意味論的な観点で各コンポーネントに対する操作をテストシナリオとして記述したいのです。

 例えばリストを選択するために隠れている部分をスクロールする、などといった動作は人間の頭ではあたりまえのように処理していて、意識していません。テストシナリオを書く場合も同じことで、選択対象の項目が隠れている、いないに係らず、単純に「リストのこの項目を選択する」と記述したいわけです。単なるスクロールのテストをするということでなければ、テストシナリオには書かずとも対象の項目をちゃんとスクロールして見えるようにしてから選択する、というレベルのことは自動的に処理してくれるようなテストツールが必要です。

 また、テストケースが設計文章の役割をするためには、チームの全員が設計文章を作成できる、つまり全員がユーザーI/Fのテストを作成できる、手軽に使えるものであることが望まれます。

 筆者の1人(安中)はこの問題に取り組むために、SwingUnitという、Java SwingアプリケーションのユーザーI/Fのテスト自動化のためのツールをjava.net上で開発中です。興味のある方はぜひチェックしてみてください。

Copyright © ITmedia, Inc. All Rights Reserved.

注目のテーマ