本文へジャンプ

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

送信されたすべての情報は安全です。

  • 閉じる [x]

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


送信されたすべての情報は安全です。

  • 閉じる [x]

エクストリーム・プログラミングの神秘を解く: テスト主導型プログラミング

最初にテストを書いてコードを単純にする

Roy Miller (roy@roywmiller.com), Independent consultant
Roy W. Millerは最初にAndersen Consulting (現在のAccenture)にて、10年以上技術コンサルタント、ソフトウェア開発者、および指南役を務め、ノースキャロライナのRoleModel Software社にてほぼ3年を過ごしました(ここでは彼は、エクストリームプログラミング(XP)を使用したJava言語アプリケーションの構築に注力しました)。現在、彼は独立コンサルタントであり指南役です。彼は、XPを含む重要なメソッドおよび機動的なメソッドを使用しており、Addison-Wesley XP Series (Extreme Programming Applied: Playing to Win)中で本を共同執筆しました。彼の最も最近の本であるManaging Software for Growth: Without Fear, Control, and the Manufacturing Mindsetは、どのように、複雑な技術がソフトウェア開発を支援することができ、そして他のITマネージャー達が、プログラマをコントロールしたり枯らすことなく、彼らのチームが実際の人々が使用して楽しいような素晴らしいソフトウェアを作成することの、支援方法を理解するかを述べています。Royの連絡先は、roy@roywmiller.comです。

概要: テスト主導型プログラミングは、プログラマーの意表をつくXPの特徴の1つです。プログラマーの多くは、その意味およびその実行方法について誤った認識を持っています。今回の記事で、XPコーチでありJava開発者のRoy Miller氏は、テスト主導型プログラミングとは何か、なぜそれがプログラマーの生産性と品質を飛躍的に向上させるのか、およびテストを書くために必要な技法について説明します。

日付:  2003年 4月 22日
レベル:  中級 この記事の原文:  英語
アクティビティー: 1487 ビュー
お気軽にご意見・ご感想をお寄せください: 


これまでの50年間、テストはプロジェクトの最終段階に行われるものと考えられてきました。もちろん、プロジェクトの進行中に行われる統合テストなど、多くのテストはコーディングがすべて 終了してから開始されるものではありません。しかし、通常はプロジェクトの後半で行われます。XPの提唱者は、このモデルは完全に逆向きの方法であると考えています。プログラマーはコードを記述する前 にテストを書き、その後、テストに合格するのに必要なコードだけを書くべきなのです。この方法により、システムをきわめて単純なものにすることができます。


まずテストを書く

XPのテストには、プログラマー・テスト とカスタマー・テスト の2つのタイプのテストがあります。テスト主導型プログラミング (テスト・ファースト・プログラミングとも言う) は、ほとんどの場合、前者のプログラマー・テストのことを指しています (少なくとも、私がこの用語を使用する場合はそうです)。テスト主導型プログラミングでは、プログラマー・テスト (単体テストとも言う。これも単にどの用語を使用するかの違いです) が記述すべきコードを導いてくれます。このことは、コードを記述する前にテストを実行する必要があるということを意味します。テストは、記述する必要がある コードを指示することによって、書くべきコードを導きます。プログラマーは、作成したテストに合格するために必要なコードだけを記述します。それ以上のコードでもそれ以下のコードでもありません。XPのルールは単純です。つまり、プログラマー・テストを行わなければ書くべきコードが分からず、したがって、コードはまったく記述されません。



最初にテストを書く方法

テスト主導型プログラミングの理論は素晴らしいものですが、最初に テストを書くにはどうすればいいのでしょう。まず、Kent Beck氏の著書「Test-Driven Development: By Example」(参考文献を参照) にある、本全体にわたる広範なサンプルに目を通すことをお勧めします。この本は、テストを作成し、テストによりコードを導く方法の仕組みだけでなく、テスト主導型プログラミングの利点についても説明しています。ここで、テスト主導型プログラミングを実感していただくために、簡単なサンプルを紹介しましょう。

テスト主導型対テスト・ファースト

私は「テスト主導型」という用語の方が「テスト・ファースト」より好きです。なぜなら、「テスト・ファースト」では、コードを書く前にプログラマー・テストを書くための仕組みが重視されているからです。この仕組みは重要ですが、「テスト主導型」が示唆する、考え方とプログラミング習慣の変更こそがXPの真の威力です。より広義の意味を持つ「テスト主導型プログラミング」という用語は両方のテストを包含し、ほとんどすべてのことをテストによって導くというXPチームが重視する姿勢を表しています。

Person オブジェクトを持つシステムを記述すると仮定しましょう。私はすべてのPerson に、要求に応じてその年齢を整数で答えさせたいと思っています。まだコードは全く書いていませんが、テストを書き始めます。読者であるあなたの反応は次のようなものでしょう。「テストだって? まだ何をテストするのかさえ分かっていない状態だ。いったいどうやってテストを書けばいいんだ。」この問いに簡単に答えます。あなたは何をテストするのか知っています。ただ、このような思考方法に慣れていないため、知っているということを知らない だけなのです。具体的にお話ししましょう。

コードはまだ書いていませんが、あなたはPerson オブジェクトのあるべき姿について認識しています。このオブジェクトには、その年齢を整数で戻すメソッドが必要です。私はJava言語で作業することが多いので、プログラマー・テストを書くのにJUnitを使用します。リスト1に、Person オブジェクトのためのJUnitテストを示します。


リスト1. PersonオブジェクトのためのJUnitテスト
                
package com.roywmiller.testexample;
import junit.framework.TestCase;
public class TC_Person extends TestCase {
  protected Person person;
  public TC_Person(String name) {
    super(name);
  }
  protected void setUp() throws Exception {
    person = new Person();
  }
  public void testGetAge() {
    int actual = person.getAge();
    assertEquals(0, actual);
  }
  protected void tearDown() throws Exception {
  }
}

最初に、JUnitになじみのない方のために基本的な仕組みについて説明しましょう。TestCase クラスは最も頻繁に使用するクラスです。TestCase をサブクラスとするテスト・クラス (この例ではTC_Person) を作成します。(注: JUnit 3.8.1では、String を取るコンストラクターを使用してもしなくても構いません。私は、Javaの開発をほとんどすべてEclipse IDE (参考文献参照) で行っており、このIDEによりコンストラクターが無償で提供されるため、コンストラクターをそのまま使用しています。) テスト・クラスを作成すると、実際の処理はテスト・メソッドで実行されます。これらのメソッドは、その性質を反映して、test という接頭部で始まります (これらはpublic なメソッドで、void を戻す必要があります)。テストを実行すると、JUnitは次のことを実行します。

  • 作成したテスト・クラスを内視(introspect)し、"test"で始まる各メソッドを実行する
  • 各テスト・メソッドの前にsetUp() メソッドを実行する
  • 各テスト・メソッドの後にtearDown() メソッドを実行する

上記の例では、setUp() メソッドの処理は複雑なものではありません。単にPerson のインスタンスを生成するだけです (ここでは、「完全な」テスト・ケースがどのようなものかを知っていただくためにこのメソッドを含めました)。これは、仮に20個のテスト・メソッドがある場合、各メソッドのテストは新規のPerson インスタンスを使って開始されることを意味します。tearDown() メソッドでは何も行われないため、現時点では空の状態です。ここで、setUp() またはtearDown() は必要ではないことを強調しておきます。通常、私は2番目または3番目のテスト・メソッドを作成して、これらすべてで共有される共通のセットアップ処理またはティアダウン処理があると判断されるまでこれらのメソッドは作成しません。

一般には行われないやり方を通じて、テスト・メソッド中で設計上の決定を行ったことに注意してください。私は、ある人物を構成し、その「デフォルト」Person は年齢0を戻すものと仮定しました。また、Person オブジェクトにはgetAge() メソッドがあるものとします。これは、当面のところ、なかなか良く出来た前提です (長くは続きませんが)。このテストは、先に進むための単純なテストであり、いわば「ポンプの呼び水」の役割を果たすものです。このような前提条件のもと、Person のインスタンスを作成し (その働きを理解するためにsetUp() メソッドを使用します)、テスト・メソッドでテスト中のメソッドを呼び出して、一連の "assert" メソッドの1つを呼び出します。assertメソッドは、ある事実の真偽を検査するメソッドです。つまり、これらのメソッドは、真であることを検証するようJUnitに指示する事柄に関するアサーションを行います。表1はアサーション・カテゴリーのリストです。


表1. アサーション・カテゴリー
assertメソッド説明
assertEquals 2つの事柄を比較して同等であるかどうかを調べます (プリミティブまたはオブジェクト)
assertTrue ブール値を真に評価します
assertFalse ブール値を偽に評価します
assertNull オブジェクトがヌルであることを確認します
assertNotNull オブジェクトが非ヌルであることを確認します
assertSame 2つのオブジェクトが同じインスタンスであることを確認します
assertNotSame 2つのオブジェクトが同じインスタンスではないことを確認します

ここでは、作成したPerson インスタンスによって戻される年齢が、新規Person オブジェクトのデフォルト値0であることを確認しています。

当然のことながら、このテストはコンパイルさえされません。図1は、テストをEclipseで実行しようとしたときに表示されるJUnit Fast Viewです。


図1. JUnitでのコンパイルの失敗
図1

お分かりのとおり、Person クラスをまだ作成していないため、テストの実行で問題が発生し、JUnitに赤いバーが表示されます。すべてのコードが実行されて、テストに合格すると、バーは緑色になります。常に緑のバーが表示されるよう努力してください。JUnitのモットーは、結局のところ、「バーはグリーンに、コードはクリーンに」というものです (異議を認めます)。

問題ありません。これからPerson クラスを作成します。リスト2をご覧ください。


リスト2. Personクラス
                
package com.roywmiller.testexample;
public class Person {
	
  public int getAge() {
    return 0;
  }
}
                

このテストを実行すると、テストに合格し、緑色のバーが表示されます。getAge() から値を何か戻す必要があります。そうしないとテストはコンパイルされません。ここでは0が便利であるため、0を新規Person インスタンスのデフォルトに設定しましたが、うまく機能しています。ここでも、テストに合格するのに必要なコードだけしか書いていません。

デフォルトの年齢を持つPerson を作成できるというのは素晴らしいことですが、今作成中のシステムにとってあまり効果はありません。Person はもっと賢くなくてはなりません。真に必要なのは、生年月日を保持して、今日現在の年齢を答えることができるPerson です。このことは、Person オブジェクトは歳をとるということを意味します。コーディングを進める前に、testGetAgetestGetDefaultAge という名前に変更し (デフォルトの年齢をテストするということを明確にするために)、リスト3に示すような、テスト・ケース用の別のテスト・メソッドを書きます。


リスト3. 新規テスト・メソッド
                
public void testGetAge() {
  GregorianCalendar calendar = new GregorianCalendar(1971, 3, 23);
  person.setBirthDate(calendar.getTime());
  int actual = person.getAge();
  assertEquals(31, actual);
}
                

このテストもまだコンパイルされません (あるパターンにお気付きですか?)。これは、PersonsetBirthDate() メソッドがないからです。このメソッドを作成すると、Person はリスト4に示されたもののようになります。


リスト4. 更新されたPersonクラス
                
package com.roywmiller.testexample;
import java.util.Date;
public class Person {
	
  protected Date birthdate;
	
  public int getAge() {
    return 0;
  }
	
  public void setBirthDate(Date aBirthDate) {
    this.birthdate = aBirthDate;
  }
}
                

PersongetAge() に対して、以前と異なる操作をまだ実行しないので、テストは失敗します。図2は、このとき表示されるJUnit Fast Viewを示しています。


図2. JUnitでのアサーションの失敗
図2

AssertionFailedError が生成されて、結果は31ではなく0であることが報告されます。私は異なる操作を実行するようメソッドを変更していないので、この失敗は予期されたものです。これで、テストに合格するのに必要なだけのコード (2つのコードがあります) を書くことができます。デフォルトの年齢0を設定できるようにし、かつ、誕生日が1971年3月23日の人の年齢を計算するためのコードが必要です。一部のプログラマー (Kent Beck氏も含む) は、この時点で、非常に単純化した手段を取ることを勧めるでしょう。例えば、birthdate を検査してヌルかどうかを確認し、ヌルの場合は0を、非ヌルの場合は31を戻すようにした後、別のテストを書いて、計算方法をさらに洗練させるなどの方法です。この方法は、プログラミングを小さなステップごとに検討し、デバッグ作業から抜け出すために基本の規律に立ち戻るときの優れた実践手法です。しかしここでは、例を簡単なものにしておきたいので、Calendar を使用して私の好む方法で年齢を計算し、単にこのテストに合格するようにしたい思います。リスト5はPerson に記述する内容です。


リスト5. getAge() の実装
                
package com.roywmiller.testexample;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
public class Person {
	
  protected Date birthdate;
	
  public int getAge() {
    if (birthdate == null)
      return 0;
    else {
      int yearToday = Calendar.getInstance().get(Calendar.YEAR);
			
      Calendar calendar = new GregorianCalendar();
      calendar.setTime(birthdate);
      int birthYear =calendar.get(Calendar.YEAR);
			
      return yearToday - birthYear; }
  }
  public void setBirthDate(Date aBirthDate) {
    this.birthdate = aBirthDate;
  }
}
                

テストを実行すると、予期された値は31であるが、戻されたのは32であることが報告され、テストは失敗します。何がいけないのでしょう? 問題は今書いたコードにあることは分かっていますが、それほど多くのコードは書いていません。else 節を検査すると、年のみに基づいて年齢を計算していることに気付きます。これではだめです。私は現在31歳ですが、今月後半には32歳になるので (このコードを書いた3月時点で)、作成したアルゴリズムによる結果は間違ったものになります。getAge() にはこの事実を反映する必要があります。問題を解決するために、リスト6に示すコードを使用します。


リスト6. 修正版getAge()
                
else {
  int yearToday = Calendar.getInstance().get(Calendar.YEAR);
  Calendar calendar = new GregorianCalendar();
  calendar.setTime(birthdate);
  int birthYear = calendar.get(Calendar.YEAR);
	
  if (yearToday == birthYear)
    return yearToday - birthYear;
  else
    return yearToday - birthYear - 1;
}
                

緑色のバーです!Person クラスにはコードの重複がいくつかありますが、後でリファクタリングを実行するためにそのままにしておきます。ご自身で自由にコードをクリーンアップしてください。自身を持ってクリーンアップできるはずです。なぜなら、何も破壊していないことを確証するためのテストがあるからです。

この例により、テスト主導型プログラミングの概要がお分かりいただけると思います。私は、各ステップでテストに合格するのに必要なコードしか書いていません。これは、機械的な課題であると同時に思考の挑戦でもあります。コードを書く前に テストを書くべきであるという考え方に慣れる必要があります。すべてのテストに合格したとき、仕事は完了します。

最初にテストを書く場合、意図的に視野を狭くすることに慣れる必要があります。リスト6の例はささいな事例です。通常、実世界のプログラミングの問題は、最も単純なケースにおいても、これより複雑になるものです。これらの問題を、もっと処理しやすい小さな問題に分割することは役に立つ方法ですが、それでも頭の痛い課題が残ってしまうことがあります。このような場合、ずっと先のことを考えて、どのように「一般化」すべきか、または、まだ見えない問題にどう対処すべきかについて推測したくなる衝動を抑えてください。単にテストを書き、それに合格するようにしてください。小さなステップを作成し、次のステップへの移行を促すようなテストを書く必要があります。コードをテストして現実のコードにしているのだということを忘れないでください。小さなステップに従ってテストを実行すれば、道を踏み外すことはありません。


なぜそうすべきなのか. . .

最初にテストを書くというのはあまり良い考えではないと思うかもしれません。これはあまりに風変わりなものに見えるかもしれませんし、不要なものに思えるかもしれません。私は、この2番目の理由をよく耳にしますが、そのほとんどが経験豊富なプログラマーからのものです。彼らは賢く、豊富な経験を持っており、自分が何を行っているかを知っているので、最初にテストを書く必要はないと言います。確かにそのとおりです。しかし、「最初にテストを書くことから学び取ることは何もない」という彼らの暗黙的な主張には疑問があります。謹んでこれに反対します。実際、最初にテストを書くことの理由には次の3つがあると思います。

  • 学習
  • 創発性
  • 自信

まずテストを書き、それを後で実行するというのは優れた学習方法です。これにより、作成中のシステムのインターフェースに注力し続けることができます。テストを作成する際、使用中のクラスがすでに存在しているものと仮定し、システムの残りの部分でそのクラスを自分の好む方法で使用します。後でそのクラスの使用方法が分からなくなった場合は、テストを参照し、非常に具体的な例を確認することができます。これは学習に最適の方法です。

最初にテストを書くことの最も興味深い特徴の1つに、創発を可能にするということがあります。作成中のシステムを成長させているのです。XPを使用している場合、すべての要素を前もって設計するのではなく、設計しながら発展させています。最初にテストを書いてそれに合格すると、実行したい機能や特徴をコード自身に語らせることができます。コーディングを始めたばかりの場合、自分で作成した仮定を単にビルドしていきます。コードの最終決定を遅らせれば遅らせるほど、新しい事実や新しい方向性を発見する機会が増え、システムをより優れたものにすることができます。

しかしながら、最初にテストを書くことの最大の利点は、後でもテストを実行できることです。最初にテストを書く際、私は非常に広範囲の論理を取り込みます。コードのすべて取り上げることはできないにしても、その多くはカバーします。どんな場合でも、私がかつて担当したXP以前のほとんどのプロジェクトに勝る一連のテストが作成できます。ボタンを押すだけで、これらのテストを実行できます。数秒後には、私が指示したとおりにコードが実行されるかどうかが分かります。この一連の退行テスト(regression test)は非常に有効なものです。私のチーム (あるいは別のチーム) のメンバーは誰でも、いつでも (リリースの前日であっても) コードを変更できます。これは、何か故障が発生すれば、テストがすぐにそれを教えてくれるからです。このことは、プログラマーとしての自信を私に与えてくれます。この自信は、多くのプログラマーが持っていたものよりもさらに大きな自信です。


. . . そして、なぜそうしないのか

「エクストリーム・プログラミングの神秘を解く」の未来を形成するために

これまでと同様、今後のコラムのためにフィードバックをお寄せいただき、このコラムを形作る手助けをお願いします。XPについて最も疑問に感じるのはどのような点ですか? まったく非常識で、浅はかであり、プロらしくない不可能な考え方だと思われるのはどの部分ですか? 最も分かりにくいプラクティスはなんですか? 直接私にメールを送ってください。

最初にテストを書かないプログラマーの多くは、最初にテストを行う選択肢があることさえも知りません。彼らはその選択肢があることを知れば、その実行方法について混乱し、なぜそうしなければならないのか疑問に思うかもしれません。彼らがその方法を知っていて、良い考えだと認める場合でも、多くのプログラマーはテストを最初に書こうとしません。

最初にテストを書くには、規律が必要です。私もプログラミングを行う際、作成中のシステム用のテストを書かないほうが楽だと思うことがよくあります。それが真実であることもありますが、通常は、短期間のことでしかありません。頻繁にテストを書かないことがあると、テストが実施されていないコードがすぐに山積みになります。次のシステム機能をコーディング中に、何か問題が発生した場合、どこに問題があるのでしょう。テストがなければ、自信を持ってこの質問に答えられません。すべてがうまくいっているように見える場合でも、後で現れるような故障はシステムに存在しないと断言することはできません。このような悪循環は、コードに何か不備があることを報告するテスターをプログラマーが嫌う理由になっています。テストなくしてバグを追跡する作業は、深夜に及ぶ残業と仕事に対する不満を生む原因といえます。

このように説明すると、ほとんどのプログラマーは、テスト主導型プログラミングは素晴らしい考え方だと言いますが、それでもまだ実行しようとしません。コードの前にテストを書くということは、テストが実行されて失敗するまで、仕事の面白い部分に着手できないということです。このわなにひっかからないでください。後で非常に高くつくことになります。


困難なケース

テストを書き始めると、必ず、「これをテストする方法はない」と思う状況に遭遇します。XPのコミュニティーには、テストを行わずにコードを書いてはならないとはっきり断言する人もいます。打ちのめされるまで試してください。ただし、私の経験では、テストを実行できないケースが幾つかありました。このような状況に陥ったとき、あきらめますか? ある程度はあきらめます。しかし、この場合、出来ることには次の2つがあると考えます。

  • コードを書いてからテストを書く
  • ほとんどないケースとして、最初は全くテストを書かずに、後でテストに戻る

最初にテストを書こうとしてテストが書けないことが分かった場合、私はテストを後で書くことにします。私は、このような状況においても、完全なレグレッション・テストによる自信を得るためテストを実行したいと思います。ただし、それには最初にコードを幾つか書いて、その後でテストを書く必要があります。時には、小さなコードの後に小さなテストを書いて、それらを一緒に展開させます。めったにないケースですが、テストの書き方が全く分からない場合もあります。このような場合で、ペアのパートナーもその方法が分からないときは、他の誰かに (例えば、別のペア) に良い考えはないかどうか尋ねます。これにより、解決を見ることもありますが、チーム全体が行き詰まってしまうこともあります。このような場合は、実用性を重視する必要があります。コーディングをやめて、絶望に打ちひしがれるか、テストを行わずにコードを幾つか書いて、後でテストに戻るかです。おそらく、このコードで現れる最初のバグにより、テストすべき項目とその方法がより明確になってくるでしょう。実用主義がルールです。


テスト・ツールおよび技法

ほとんどすべての言語用にxUnitライブラリーが提供されています。JUnitは、Javaプラットフォーム用の作業を非常に効率よく実行します。私は個人的に、Eclipse IDE (参考文献参照) を使用していますが、これにはJUnitが非常に良く組み込まれています。Eclipseはオープン・ソースであり、一連の実行可能な独自のテストを備えています。正しい基盤に基づいて、多くの優れたテストを作成できます。ただし、他のツールをもう少し使用すると効果的な場合もあります。幸運にも、テストが実行不可能に見える 場合でも、テストの実行を容易にすることができる幾つかのコーディング技法があります。使用できる技法にはObjectMother パターン、疑似 (Mock) オブジェクト、および偽 (Sham) オブジェクト があります。

ObjectMotherパターン

ObjectMotherパターン (実際には、四人組 (Gang of Four) 抽象ファクトリー・パターン (Abstract Factory pattern) (参考文献参照) の1実装です) は、テスト対象となるもののインスタンスを生成するための、ファクトリー・オブジェクトを作成する方法を教えてくれます。例えば、セミナーの参加者の予約を受け付けるシステムを構築するとしましょう。特定の機能のテストに使用できる多様な特性を持つSeminar オブジェクトを作成する、ObjectMotherオブジェクトを構築できます。Java言語では、TF_Seminar と呼ばれるオブジェクトを作成し、それにcreateSomething またはnewSomething などの名前の静的なファクトリー・メソッドを幾つか設定できます。この "something" は、作成中の機能を説明する語句に置き換えます。例えば、すべてのデータ・メンバーに既知のデータを取り込んだSeminar を作成する場合はnewFullyLoaded のように変更します。このようにすることで、テスト・データを1箇所に集め、コードをクリーンにしてリファクタリングしやすくすることができます。コード内でのテストのために、完全にロードされたSeminar が必要な場合は、リスト7に示すように記述します。


リスト7. ObjectMotherの例
                
Seminar seminar = TF_Seminar.newFullyLoaded();
seminar.doSomething();
assertEquals("expectedValue", seminar.getValue());

疑似オブジェクト

疑似オブジェクトにより、テスト用のオブジェクトを疑似的に作成することができます (参考文献参照)。実際のコンポーネントに実装したいインターフェースを疑似オブジェクトに設定し、実際のコンポーネントの形が明確になるまで疑似オブジェクトを使用します。ただし、疑似オブジェクトは単に、存在していないコンポーネントのスタブであるだけではありません。プログラマーは、コードと疑似オブジェクトがどのように相互作用するかを検証できます (例えば、メソッドの呼び出し回数の検証、状態のチェックなど)。最近、疑似オブジェクトは非常にもてはやされていますが、私の考えでは疑似オブジェクトは使われすぎで、多くの場合、実際の作業で使用するには重すぎます。

偽オブジェクト

同じインターフェースを実装し、テスト内で、そのインターフェースとの対話方法について特定の質問に答えてくれるような偽物のオブジェクトだけが必要な場合もあります。これが偽オブジェクトです。つまり、テスト内で見せかけの動作を実行する軽量な方法です。偽オブジェクトは、必要に応じてどのようなものにでもできます。これは、かつて私が使用した中で最も用途の広いツールであり、パターンであり、かつ考え方です。是非、ツールボックスの中のツールとして使用してみてください。例えば、以前に作成したPerson オブジェクトの偽オブジェクトは、リスト8に示すようなものになります。


リスト8. Personオブジェクトの偽オブジェクト
                
protected class ShamPerson extends Person {
  protected boolean getAgeWasCalled;
  public int getAge() {
    getAgeWasCalled = true;
    return 25;
  }
}

実際の場合は必ず、テスト中のクラスの偽オブジェクト (この場合はShamPerson) をテスト内の内部クラスとして組み込むことにしています。そうすることで、偽オブジェクトを必ずしも必要としない他のクラスやオブジェクトから偽オブジェクトを隠すことができます。

偽オブジェクトを作成すると、Person を直接的に検査していないテスト内でこれを使用することができ、また一方で、他のコードからPerson インスタンスへの対話の仕方もテストできます。ShamPerson のインスタンスを生成して、それと対話し、getAgeWasCalled が真であることを断言 (assert) することができます。


プログラミング革命

コードを書く前にテストを書くことは、プログラマーとしての私の生き方を根本から変えるものでした。そして、それは読者の皆さんにも同じことが言えるはずです。私のコードは、最初にテストを書く前のコードと比べると、一貫して、より単純かつクリーンであり、さらに堅固なものになりました。コードを書く前にそれをテストする方法について考えるという訓練をするだけで、以前よりも良いコードが書けます。すべてのソフトウェア開発チームが他のXPプラクティスを使用せず、単に最初にテストを書くようにしたとしても、ソフトウェア開発業界は驚くほど改善されることでしょう。少し訓練するだけで、どんなプログラマーも最初にテストを書けるようになります。ツール (JUnitやEclipseなど) は無償で提供されています。必要なのは練習だけです。費やした投資がやがて報われるのを体験してきました。皆さんもまたそうであると確信しています。


参考文献

  • Kent Beck氏の著書『Test-Driven Development: By Example』にある、最初にテストを書く方法についての広範なサンプルをご覧ください。面白く、またためになる本です。

  • JUnit は、Java言語でプログラマー・テストを書くための優れたツールです。このサイトは数多くの書籍やツールにリンクされています。

  • Malcolm Davis氏は、彼の記事「AntとJUnitを用いた漸進的開発」(developerWorks、2000年11月) の中で、単体テストによってコードを改善し、作業時間を短縮する方法について解説しています。

  • 疑似オブジェクトの詳細はwww.mockobjects.com に掲載されています。

  • Nick Lesiecki氏による記事「AspectJおよび疑似オブジェクトによる柔軟なテスト」(developerWorks、2002年5月) では、テスト・ケースの分離にまつわる問題を紹介し、疑似オブジェクトとAspectJを使用して正確で堅固な単体テストを開発する方法について説明しています。

  • まだEclipseを使用したことがない場合は、Eclipse を参照してみてください。これはXPベースの開発に最適の環境です。

  • Design Patterns』(Erich Gamma他著、Addison-Wesley、1995) は、この主題についての最も信頼のおける本です。

  • developerWorks の 『エクストリーム・プログラミングの神秘を解く』 シリーズでは、XPのプラクティスが (個人的な意見もありますが) 詳しく紹介されています。記事「価値を重視する」(2003年2月) では、XPストーリー・ポイント、およびプロジェクト・チームにおけるその意味が詳しく説明されています。「「XPの真髄」に立ち戻る: 第2回」(2002年9月) では、ペア・プログラミングの構造が説明されています。

  • XPチームにとってVisualAge for Javaが優れたツールであることを理解するには、「Extreme Programming with IBM VisualAge for Java」(developerWorks、2001年4月) をお読みください。

  • developerWorks Javaテクノロジー・ゾーン には、Javaプログラミングのあらゆる側面についての豊富な記事が用意されています。

著者について

Roy W. Millerは最初にAndersen Consulting (現在のAccenture)にて、10年以上技術コンサルタント、ソフトウェア開発者、および指南役を務め、ノースキャロライナのRoleModel Software社にてほぼ3年を過ごしました(ここでは彼は、エクストリームプログラミング(XP)を使用したJava言語アプリケーションの構築に注力しました)。現在、彼は独立コンサルタントであり指南役です。彼は、XPを含む重要なメソッドおよび機動的なメソッドを使用しており、Addison-Wesley XP Series (Extreme Programming Applied: Playing to Win)中で本を共同執筆しました。彼の最も最近の本であるManaging Software for Growth: Without Fear, Control, and the Manufacturing Mindsetは、どのように、複雑な技術がソフトウェア開発を支援することができ、そして他のITマネージャー達が、プログラマをコントロールしたり枯らすことなく、彼らのチームが実際の人々が使用して楽しいような素晴らしいソフトウェアを作成することの、支援方法を理解するかを述べています。Royの連絡先は、roy@roywmiller.comです。

不正使用の報告のヘルプ

不正使用の報告

ありがとうございます。 このエントリーは、モデレーターの注目フラグが設定されました。


不正使用の報告のヘルプ

不正使用の報告

不正使用の報告の送信に失敗しました。


developerWorks: サイン・イン


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。 プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。 お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

表示名をお選びください

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

(半角英数字で3文字以上31文字以下にする必要があります)


「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


この記事を評価する

コメント

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology
ArticleID=224164
ArticleTitle=エクストリーム・プログラミングの神秘を解く: テスト主導型プログラミング
publish-date=04222003
author1-email=roy@roywmiller.com
author1-email-cc=

タグ

Help
このタグで、My developerWorks のすべてのタイプのコンテンツを見つけるために検索フィールドを使用します。

スライダーバーを使用することで、より多く(少なく)タグを表示します。

人気のタグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するトップのタグを表示します。

マイ・タグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するお客様ご自身のタグを表示します。

このタグで、My developerWorks のすべてのタイプのコンテンツを見つけるために検索フィールドを使用します。人気のタグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するトップのタグを表示します。マイ・タグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するお客様ご自身のタグを表示します。