JavaA2Z

KAB-studio > プログラミング > JavaA2Z > テストファーストとは

テストファースト

日本語 製造前試験
英語 test first
ふりがな てすとふぁーすと
フリガナ テストファースト

解説

プログラム実装する前に、先にテストプログラムを作ること。
主にJUnit単体テストプログラムの作成方法を指す。
古くからのプログラムのテスト方法は、まずプログラムを作り、そしてそれをテストするというものだった。
逆にテストファーストでは、publicメソッド呼び出し部分(メソッド名、引数戻り値)のみを作り、そのメソッドをテストするテストプログラムJUnitを使用して先に作成する。そのあとにメソッド実装するため「テストファースト(テストが先)」と呼ばれる。
 
実際の作成方法としては、まずクラスpublicメソッド呼び出し部分のみを作り、実装は何も作らない。ただしコンパイルエラーになっては意味がないため、適当な戻り値を返すようにする。この時、実装はしてはいけない。
次に、publicメソッドをテストするテストメソッドを作成する。通常1クラスにつき1テストクラスTestCaseクラスサブクラスとして作り、publicメソッド1つにつきテストメソッド1つを作成する。
テストメソッドに、テスト対象のpublicメソッドのテストプログラム実装する。通常は数とおりのテストをうようにする。また、値のチェックはassertEquals()メソッド等でう。
この段階でテストを実する。当然、テストは失敗する。
最後に、このテストが失敗しないよう、publicメソッド実装する。そうすれば、晴れてテストは成功し、プログラムは完成することになる。
 
テストファーストの利点は、「契約による設計」によって「明確な仕様を決める」という点にある。
メソッドには、仕様という名の「契約」が存在する。引数に渡せる値の範囲、それによって返される戻り値の種類、等が決められる。だが、仕様書という形で明示されていない場合、その仕様は曖昧になりがちである。
その曖昧さを取り除くのがテストファーストである。プログラム実装前に、まずメソッドの仕様を決める事で、曖昧になりがちなメソッド実装を確かなものとする。
これは「契約による設計」という手法に基づいた方法であり、より安全なプログラムを作ることができる。
 
テストはことある毎に実することで、常に「これまでに実装したメソッドが、今も正しく実装されている」ことを確認することができる。
他のメソッドを変えたためにバグが産まれる「デグレード」を常にチェックすることができるため、新たな機能の追加や、「リファクタリング」といったプログラムの修正がしやすくなる。
これも、テストファーストにより「先に必ずテストプログラムを作っている」からできることである。後からテストプログラムを作っていくと必ずテスト漏れができるが、テストファーストを守ればそれはなくなる。
よって、常にプログラムの整合性が検証されることになり、品質が高まる。
 
ただし、問題も少なからずある。
まず、テストプログラムそのものの工数が馬鹿にできない。総合的に見れば、テストファーストの方が品質を保てるため工数はテストファーストの方が少なくて済むが、そもそもの工数に「バグが出た場合の修正工数」など含まれておらず、テストプログラムの分だけ明かに工数が増えることは確かなため、上司や客先を説得する材料に乏しい。
次に、仕様変更に弱い。仕様変更がわれた場合、それに伴いテストプログラムを修正しなければならない。仕様が完全に決まっていない案件の場合、テストファーストで始めていても、プロジェクト終盤では形骸化する可能性が高い。
テストプログラムそのものにバグが含まれる可能性もある。テストプログラムは分量が多く、手を抜きやすく、さらにコピペが多いため、単純な記載ミスが頻発しやすい。テストプログラムが誤っているとどうしようもなくなる。
システム上テストが難しい場合もある。データベースファイルへの書き込みが伴う場合、それらのテスト環境がなければテストはできない。またテスト環境があったとしても、その環境が本番環境と異なれば完全なテストとは言えなくなる。
テストファーストは最近生まれた概念のため、浸透に時間が掛かる。特に、テストファーストは「プログラムの作成をミスする可能性がある」という「性悪説」に基づいて作られており、無意味にプライドの高いプログラマーから敬遠される傾向にある。
テストファーストをうためにはクラスメソッド間の関係が疎である必要があるため、旧来のプログラムの作り方には合わず、プロジェクト全体でテストファーストを実できない場合も多い。
 
テストファーストは、ある意味理想論のシステムであるとも言える。
だが、もし実現できれば、そのプロジェクトは絶対に失敗しないだろう。

参考サイト

  • (参考サイトはありません)

(KAB-studioからのおしらせです)

サンプルプログラム(とか)サンプルを別ウィンドウで表示サンプルをクリップボードへコピー(WindowsでIEの場合のみ)

// Sample.java
public class Sample
{
    /**
    *   テスト対象のメソッド。
    *   このplus()メソッドができるまでの、テストファーストの
    *   流れを見てみます。1~4までを追っていってください。
    *   ※当然、本当のメソッド名はplus()とtestPlus()です。
    *    数字は便宜的に付けているものですから。
    *   →Sample#plus1()メソッドの説明につづく。
    */
    public int plus( int iL, int iR )
    {
        return iL + iR;
    }

    /**
    *   →Sample#plus()メソッドの説明のつづき。
    *   まず、クラスと、メソッドの引数と戻り値を
    *   先に作ります。
    *   また、「契約による設計」に基づいて、渡せる
    *   引数や、返される戻り値を決めます。
    */
    public int plus1( int iL, int iR )
    {
        return 0;
        // 戻り値はコンパイルエラーに
        // ならないよう適当な値を返しておきます。
        // →SampleTest#testPlus2()の説明に続く。
    }

    /**
    *   →SampleTest#testPlus2()メソッドの説明のつづき。
    *   テストを実行して、完成していないことを確認してから、
    *   メソッドを実装します。
    */
    public int plus3( int iL, int iR )
    {
        return iL + iR;
        // というわけで、ちゃんと完成させました。
        // →SampleTest#testPlus4()の説明に続く。
    }
}


// SampleTest.java
import junit.framework.TestCase;

/**
*   Sampleクラスのテストクラスです。
*/
public class SampleTest extends TestCase
{
    /**
    *   コンストラクタ。
    */
    public SampleTest()
    {
        // クラス名をセットします。
        super( SampleTest.class.toString() );
    }

    /**
    *   →Sample#plus1()メソッドの説明の続き。
    *   Sample#plus1()メソッドをテストします。
    */
    public void testPlus2() 
    {
        Sample sample = new Sample();
        // 未完成のメソッドplus()を呼び出します。
        // 未完成なので、当然誤った値が返ってきます。
        int result = sample.plus1( 2, 1 );
        // assertEquals()メソッドで結果をチェックします。
        // 第1引数に「予想される結果」を、
        // 第2引数にテスト対象の変数を渡します。
        assertEquals( 3, result );
        // これは、このメソッドの「仕様」になります。
        // このようにプログラムとして書くことで、
        // 「Sampleクラスのplus()メソッドは、
        // 戻り値に第1引数と第2引数を足した値が
        // 返される」ということを明記しているわけです。
        // これは「契約による設計」に基づいた手法です。
        // メソッドの仕様を「契約」という形で明確に
        // しているわけです。

        // でも、この段階では当然誤った値が返ってくるので
        // 例外が投げられます。

        // junit.framework.AssertionFailedError: expected:<3> but was:<0>
        //     at junit.framework.Assert.fail(Assert.java:47)
        //     at junit.framework.Assert.failNotEquals(Assert.java:282)
        //     at junit.framework.Assert.assertEquals(Assert.java:64)
        //     at junit.framework.Assert.assertEquals(Assert.java:201)
        //     at junit.framework.Assert.assertEquals(Assert.java:207)
        //     at SampleTest.testPlus2(SampleTest.java:31)
        //     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        //     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        //     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        //     at java.lang.reflect.Method.invoke(Method.java:324)
        //     at junit.framework.TestCase.runTest(TestCase.java:154)
        //     at junit.framework.TestCase.runBare(TestCase.java:127)
        //     at junit.framework.TestResult$1.protect(TestResult.java:106)
        //     at junit.framework.TestResult.runProtected(TestResult.java:124)
        //     at junit.framework.TestResult.run(TestResult.java:109)
        //     at junit.framework.TestCase.run(TestCase.java:118)
        //     at junit.framework.TestSuite.runTest(TestSuite.java:208)
        //     at junit.framework.TestSuite.run(TestSuite.java:203)
        //     at junit.framework.TestSuite.runTest(TestSuite.java:208)
        //     at junit.framework.TestSuite.run(TestSuite.java:203)
        //     at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:392)
        //     at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:276)
        //     at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:167)

        // 最初の一文の「expected:<3> but was:<0>」は、
        // 「期待した値は3だったけど、実際には0だった」という意味です。

        // 現段階では、この例外は当然投げられます。
        // また、この例外により、EclipseのJUnitビューのゲージは
        // 赤くなっているはずです。
        // つまり、この時点で「まだ完成していない」ことを確認するわけです。
        // そして、この例外が消え、ゲージが緑色になるようにしていくわけです。
        // →Sample#plus3()メソッドの説明につづく。
    }

    /**
    *   →Sample#plus3()メソッドの説明の続き。
    *   Sample#plus3()メソッドをテストします。
    */
    public void testPlus4() 
    {
        Sample sample = new Sample();
        int result = sample.plus3( 2, 1 );
        // 今度はちゃんと実装されているので、
        // 正しい値が返ってくるはずです。
        assertEquals( 3, result );
        // 値が一致しているので例外は投げられません。
        // そして、ゲージが緑色になります。
        // →これで、プログラムが完成しました!
    }
}


// Tester.java
import junit.framework.Test;
import junit.framework.TestSuite;

/**
*   このクラスを使うために、junit.jarに
*   クラスパスを通してください。junit.jarは
*   Junitをダウンロードしてくれば入っているはずです。
*/

/**
*   各テストメソッドを呼び出すクラス。
*
*   コンパイルを通すために、junit.jarにクラスパスを
*   通してください。junit.jarはEclipseに付いていますが、
*   最新版をダウンロードした方がいいでしょう。
*
*   Eclipseで実行する場合、メニューの【実行】-【デバッグ】の
*   【起動構成】から【JUnit】を選び【新規】で新規作成し、
*   【テスト・クラス】でこのクラスを指定してください。
*
*/
public class Tester
{
    /**
    *   JUnitから呼ばれるメソッド。
    *   main()メソッドみたいなものです。
    */
    public static Test suite()
    {
        // テストを登録するためのTestSuiteを作ります。
        TestSuite suite = new TestSuite();

        // テストケースを追加します。
        // このクラスのtestメソッドが呼ばれます。
        suite.addTestSuite( SampleTest.class );

        // TestSuite を返します。
        // この中に登録したテストクラスが実行されます。
        return suite;
    }
}
// Sample.java
public class Sample
{
    /**
    *   テスト対象のメソッド。
    *   このplus()メソッドができるまでの、テストファーストの
    *   流れを見てみます。1~4までを追っていってください。
    *   ※当然、本当のメソッド名はplus()とtestPlus()です。
    *    数字は便宜的に付けているものですから。
    *   →Sample#plus1()メソッドの説明につづく。
    */
    public int plus( int iL, int iR )
    {
        return iL + iR;
    }

    /**
    *   →Sample#plus()メソッドの説明のつづき。
    *   まず、クラスと、メソッドの引数と戻り値を
    *   先に作ります。
    *   また、「契約による設計」に基づいて、渡せる
    *   引数や、返される戻り値を決めます。
    */
    public int plus1( int iL, int iR )
    {
        return 0;
        // 戻り値はコンパイルエラーに
        // ならないよう適当な値を返しておきます。
        // →SampleTest#testPlus2()の説明に続く。
    }

    /**
    *   →SampleTest#testPlus2()メソッドの説明のつづき。
    *   テストを実行して、完成していないことを確認してから、
    *   メソッドを実装します。
    */
    public int plus3( int iL, int iR )
    {
        return iL + iR;
        // というわけで、ちゃんと完成させました。
        // →SampleTest#testPlus4()の説明に続く。
    }
}


// SampleTest.java
import junit.framework.TestCase;

/**
*   Sampleクラスのテストクラスです。
*/
public class SampleTest extends TestCase
{
    /**
    *   コンストラクタ。
    */
    public SampleTest()
    {
        // クラス名をセットします。
        super( SampleTest.class.toString() );
    }

    /**
    *   →Sample#plus1()メソッドの説明の続き。
    *   Sample#plus1()メソッドをテストします。
    */
    public void testPlus2() 
    {
        Sample sample = new Sample();
        // 未完成のメソッドplus()を呼び出します。
        // 未完成なので、当然誤った値が返ってきます。
        int result = sample.plus1( 2, 1 );
        // assertEquals()メソッドで結果をチェックします。
        // 第1引数に「予想される結果」を、
        // 第2引数にテスト対象の変数を渡します。
        assertEquals( 3, result );
        // これは、このメソッドの「仕様」になります。
        // このようにプログラムとして書くことで、
        // 「Sampleクラスのplus()メソッドは、
        // 戻り値に第1引数と第2引数を足した値が
        // 返される」ということを明記しているわけです。
        // これは「契約による設計」に基づいた手法です。
        // メソッドの仕様を「契約」という形で明確に
        // しているわけです。

        // でも、この段階では当然誤った値が返ってくるので
        // 例外が投げられます。

        // junit.framework.AssertionFailedError: expected:<3> but was:<0>
        //     at junit.framework.Assert.fail(Assert.java:47)
        //     at junit.framework.Assert.failNotEquals(Assert.java:282)
        //     at junit.framework.Assert.assertEquals(Assert.java:64)
        //     at junit.framework.Assert.assertEquals(Assert.java:201)
        //     at junit.framework.Assert.assertEquals(Assert.java:207)
        //     at SampleTest.testPlus2(SampleTest.java:31)
        //     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        //     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        //     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        //     at java.lang.reflect.Method.invoke(Method.java:324)
        //     at junit.framework.TestCase.runTest(TestCase.java:154)
        //     at junit.framework.TestCase.runBare(TestCase.java:127)
        //     at junit.framework.TestResult$1.protect(TestResult.java:106)
        //     at junit.framework.TestResult.runProtected(TestResult.java:124)
        //     at junit.framework.TestResult.run(TestResult.java:109)
        //     at junit.framework.TestCase.run(TestCase.java:118)
        //     at junit.framework.TestSuite.runTest(TestSuite.java:208)
        //     at junit.framework.TestSuite.run(TestSuite.java:203)
        //     at junit.framework.TestSuite.runTest(TestSuite.java:208)
        //     at junit.framework.TestSuite.run(TestSuite.java:203)
        //     at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:392)
        //     at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:276)
        //     at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:167)

        // 最初の一文の「expected:<3> but was:<0>」は、
        // 「期待した値は3だったけど、実際には0だった」という意味です。

        // 現段階では、この例外は当然投げられます。
        // また、この例外により、EclipseのJUnitビューのゲージは
        // 赤くなっているはずです。
        // つまり、この時点で「まだ完成していない」ことを確認するわけです。
        // そして、この例外が消え、ゲージが緑色になるようにしていくわけです。
        // →Sample#plus3()メソッドの説明につづく。
    }

    /**
    *   →Sample#plus3()メソッドの説明の続き。
    *   Sample#plus3()メソッドをテストします。
    */
    public void testPlus4() 
    {
        Sample sample = new Sample();
        int result = sample.plus3( 2, 1 );
        // 今度はちゃんと実装されているので、
        // 正しい値が返ってくるはずです。
        assertEquals( 3, result );
        // 値が一致しているので例外は投げられません。
        // そして、ゲージが緑色になります。
        // →これで、プログラムが完成しました!
    }
}


// Tester.java
import junit.framework.Test;
import junit.framework.TestSuite;

/**
*   このクラスを使うために、junit.jarに
*   クラスパスを通してください。junit.jarは
*   Junitをダウンロードしてくれば入っているはずです。
*/

/**
*   各テストメソッドを呼び出すクラス。
*
*   コンパイルを通すために、junit.jarにクラスパスを
*   通してください。junit.jarはEclipseに付いていますが、
*   最新版をダウンロードした方がいいでしょう。
*
*   Eclipseで実行する場合、メニューの【実行】-【デバッグ】の
*   【起動構成】から【JUnit】を選び【新規】で新規作成し、
*   【テスト・クラス】でこのクラスを指定してください。
*
*/
public class Tester
{
    /**
    *   JUnitから呼ばれるメソッド。
    *   main()メソッドみたいなものです。
    */
    public static Test suite()
    {
        // テストを登録するためのTestSuiteを作ります。
        TestSuite suite = new TestSuite();

        // テストケースを追加します。
        // このクラスのtestメソッドが呼ばれます。
        suite.addTestSuite( SampleTest.class );

        // TestSuite を返します。
        // この中に登録したテストクラスが実行されます。
        return suite;
    }
}

この単語を含むページ

「みだし」に含まれているページ

「解説」に含まれているページ

「サンプルプログラムとか」に含まれているページ

はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
Yahoo!ブックマーク 詳細を表示 users
del.icio.us 登録する RSSに登録
サンプルを別ウィンドウで表示
サンプルをクリップボードへコピー(WindowsでIEの場合のみ)
update:2005/07/21
このページは、Javaプログラミング言語についての用語を網羅した辞書「JavaA2Z」の一ページです。
詳しくは「JavaA2Z」表紙の説明をご覧ください。