のみほーだい!

のみほーだい!TOP
のみほーだい! は、(株)トラストサービス ITソリューション事業部  が運営する、技術情報交流サイトです。

アジャイル・プロセス(Agile Process)の導入について

第3回 テストファーストの考え方

@author m.matsushima

■テストファーストとは?

XPの説明でも紹介しましたが、コーディングを始める前にそのプログラムをテストするプログラムを作成することを指します。従来の方法では、要件定義→設計→コーディング→テスト環境整備→テスト実施の順で作業しましたが、テストファーストではコーディングとテスト環境整備の順番が逆になっています。
 XPの各手法の中でも特に斬新的な項目と見られがちですが、コーディング前にテスト仕様書を作成する方が効果的に開発できることは以前から知られていました。XP・RUPなどの反復的開発手法では当然テストも繰り返して行なわれるので、テストを自動的に行なえるように更にテスティングフレームワークというものが導入されました。

コーディングより先にテストの準備をする利点は、
●入力条件や処理結果が具体的な値で記述される  →  仕様の誤った解釈を防止
●例外条件、異常処理なども詳細に検討される  →  要件定義の抜けの早期発見
●細かい単位でテストできる  →  範囲が限定されバグの特定が容易
●テスト環境が財産として残る  →  デグレード防止
●誰でも同じテストを実施できる  →  品質のバラツキ防止
という点にあります。

特に強調したいのは、

「テスト仕様書はユーザー・SE間及びSE・プログラマ間の最も有効なコミュニケーション手段の1つである」
ということです。

詳細設計書レベルであれ、プログラムの仕様はどうしても抽象的になりがちで、特にユーザーにとっては、自分の言いたいことが正確に伝わっていたのか判断するのが難しいのです。テスト仕様書では実際の動作がイメージできるので、即座にユーザーが訂正を入れてくるので手戻りが少なくなります。
 また、期待はずれの動作をして「裏切られ感」を与える危険性も減ります。



注意点

いい事尽くめのテストファーストですが、注意すべき点を把握しておかないと大怪我をします。特に今までテスト工程をごまかしていた人にとっては見かけ上の工数が増大するので、文句を言わないように!

 1.個々のプログラムの開発線表にテスト準備工数を組み込むのを忘れないこと。
テスティングフレームワークというのは一度テスト用プログラムを書いてしまえば実行は数秒~数分で済んでしまい非常に効率的なのですが、そのプログラムを準備する工数が結構バカにならないのです。
テスト仕様書のユーザーレビューなどを考慮すると、コーディング工数の1.5~2倍かかると覚悟しましょう。
デザインパターン等を有効利用し、コーディングの生産性を向上させて浮いた分をテスト準備工数に割り振ります。
従来はテストなんかどうでも良いからとにかくモノだけ作ってしまうような蛮行も横行していて、ツケを納品後に回してトラブル対応に追われていましたが、今度はそのようなインチキが通用しません。XPで説明したスコープを上手く制御して、必要なプログラムから確実に片付けていくことが重要です。
早期かつ頻繁に価値のあるソフトウェアを納品することによりユーザーを満足させることを最優先とします。
 2.テスト用プログラムのバグに注意すること。
テスト用であれプログラムである以上、当然バグを含んでいることを想定すべし。特に処理結果の判断ミスにより、誤動作を正常と判断して見過ごしていないか常に留意しなくてはなりません。事情が許せば複数人によるソースレビューを行ないます。

■JUnit

特徴

 ・自動化
JUnitはテストを自動実行するだけでなく、テスト結果も自動的にチェックする。
結果はビジュアル的に分かり易く、エラー時は間違った箇所を指摘してくれる。
 ・容易なテストの記述
テスト用のクラスライブラリが充実しているのでテスト用コードを手早く書ける。
 ・テストケースの階層化
複数のテストケースをグループ化することができ、更にそのグループもグループ化できるために全体をツリー型に構成して全体を分かり易く整理することができる。

構造

・インターフェイス
インターフェイス 説明
junit.framework.Protectable 保護された環境で実行できる
junit.framework.Test テストのクラスで実際のテストを実行し結果を収集する
junit.framework.TestListener テスト進行のためのリスタ

・クラス
クラス 説明
junit.framework.Assert テスト結果を判定するクラス
junit.framework.TestCase 複数のテストを実行するクラス
このクラスで複数のテストケースを定義する
junit.framework.TestFailure テストエラーのクラス
このクラスのインスタンスが1つのエラーに対して1つ作成される
エラー番号・説明・ソース情報・トレースメッセージを管理する
junit.framework.TestResult テスト結果のクラス
実行したテストの数と発生したエラー情報、失敗したテスト情報を管理する
junit.framework.TestSuite 複数のテストを管理するクラス
複数のテストを1つのグループとして扱うときに使用する

・クラス
クラス 説明
junit.framework.Assertion.FailedError アサート時にスローする

インストール

 ・入手先
  JUnitのWebサイト(http://www.junit.org/)から最新のzipファイル(2005.2現在:junit3.8.1.zip)をダウンロードします。
 ・設定
  ダウンロードしたzipファイルを任意のフォルダに展開後、環境変数(クラスパス)にそのパスを追加します。
  以下の説明では「C:\junit3.8」に展開したものとしています。
  Windows95/98の場合
    autoexec.batをエディタで編集し「classpath C:\junit3.8\junit.jar」を追加します。
  WindowsNTの場合
    「システムのプロパティ」ダイアログボックスの「環境」タブを選択する。
    「変数:」欄に環境変数として「classpath」を入力し、「値:」欄に「C:\junit3.8\junit.jar」を入力します。
    既にclasspath変数が登録されている場合は、パスを追加します。
  Windows2000の場合
    「システムのプロパティ」ダイアログボックスの「詳細」タブを選択します。
    「環境変数:」ボタンをクリックしユーザー環境変数欄に「classpath」を登録します。
    (登録内容はWindowsNTの場合と同じ)
  尚、一時的に環境変数へパスを設定する場合はsetコマンドを使用します。


動作確認

 JUnitに付属するサンプルプログラムをTestRunnerで動作させセットアップが正しく完了したか確認します。
 CUI版TestRunnerでの確認
  prompt>java junit.textui.TestRunner junit.samples.AllTests
 AWT版TestRunnerでの確認
  prompt>java junit.awtui.TestRunner junit.samples.AllTests
 Swing版TestRunnerでの確認
  prompt>java junit.swingui.TestRunner junit.samples.AllTests


ファイル構成

フォルダ/ファイル 説明
doc/ 各種ドキュメント
javadoc/ APIリファレンス(JavaDoc形式)
junit/ JUnitのサンプル及びソースコード
junit.jar JUnitテスティングフレームワーク及びツール
src.jar JUnitテスティングフレームワークのソースコード
readme.html Readmeドキュメント

テストプログラム作成手順

 説明のために簡単なサンプルクラスを作成します。

  クラス名:SampleForJunitTest
  コンストラクタ:SampleForJunitTest (int iVal);
引数で渡された値を保持します。
引数のチェックは行いません。
  メソッド:int getValue()
現在保持している値を返します。
  メソッド:int doAddition(int iVal) throws IllegalArgumentException
現在保持している値に引数で渡された値を加算した結果を返します。
結果はprivateなフィールドとして保持します。
引数として負の値が渡されたときはIllegalArgumentExceptionを投げます。
  publicフィールド:なし
  テストケース1:コンストラクタのテスト
確認事項:引数で渡された値がprivateなフィールドとして保持されていること
  テストケース2:アクセサのテスト
確認事項:getterメソッドがフィールドに保持されている値を正しく返すこと
  テストケース3:演算処理テスト
確認事項:演算結果が正しく返されること
  テストケース4:例外テスト
確認事項:引数に負の値を渡した時に例外が発生すること

① テスト対象クラスのスケルトン作成

package jp.co.itd.junit_sample;

public class SampleForJunitTest {

    private int _iValue;    // 計算結果を保持するprivateフィールド

    public  SampleForJunitTest(int iVal) {
    }

    public  int getValue() {
    }

    public  int doAddition(int iVal) {
    }
}

 テストファーストと言ってもインタフェース部分は作成しておかないとテストプログラムのコンパイルも通らないのでクラス図に基づき最低限の定義はしておきます。


② テストコード記述

  テストケース1用

import  junit.framework.*;
import  jp.co.itd.junit_sample.*;
public class TestConstructor extends TestCase
{
    private SampleForJunitTest obj;

    // コンストラクタ
    public TestConstructor(String name) { Super(name); }

    // 初期設定
    protected void setUp() { obj = new SampleForJunitTest(10); }

    // テスト実行
    public void runTest() {
        testSampleConstructor();
}

// 終了処理
public void tearDown() { }

    // コンストラクタのテスト
    public void testSampleConstructor()
    {
        Class objClass = obj.getClass();                       // Class オブジェクト取得
        Field objField = objClass.getDeclaredField("_iValue ");  // Field オブジェクト取得
        objField.setAccessible(true);                          // フィールドをアクセス可能に
    assertEquals("_iValue", objField.getInt(obj),10);        // 仕様通りなら_iValueが10の筈
    }
}

  テストケース2用

import  junit.framework.*;
import  jp.co.itd.junit_sample.*;
public class TestAccessor extends TestCase
{
    private SampleForJunitTest obj;

    public TestAccessor (String name) { Super(name); }
    protected void setUp() { obj = new SampleForJunitTest(20); }
    public void runTest() {
testGetValue();
    }
    public void tearDown() {}

    // getterメソッドのテスト
    public void testGetValue()
    {
        assertEquals("getValue", obj.getValue(),20);
    }
}

  テストケース3用

import  junit.framework.*;
import  jp.co.itd.junit_sample.*;
public class TestOperator extends TestCase
{
    private SampleForJunitTest obj;

    public TestOperator (String name) { Super(name); }
    protected void setUp() { obj = new SampleForJunitTest(50); }
    public void runTest() {
        testDoAddition();
        testSaveValue();
    }
    public void tearDown() {}

    // 演算処理のテスト
    public void testDoAddition()
    {
        assertEquals("doAddition", obj. doAddition (40),70);
    }
// データ保持の確認
public void testSaveValue()
{
        assertEquals("saved value", obj.getValue(),70);
    }
}

  テストケース4用

import  junit.framework.*;
import  jp.co.itd.junit_sample.*;
public class TestException extends TestCase
{
    private SampleForJunitTest obj;

    public TestException (String name) { Super(name); }
    protected void setUp() { obj = new SampleForJunitTest(60); }
    public void runTest() throws Exception
    {
        testSampleException();
    }
    public void tearDown() {}

    // 例外処理のテスト
    public void testSampleException()
    {
        try {
            obj. doAddition (-80);    // わざと例外発生
            fail("IllegalArgumentException発生していない");
        } catch (IllegalArgumentException ie) {
        } catch (Exception e) {
            fail("別の例外が発生してしまった");
        }
    }

}

③ テストケースのグループ化

テストケース1~4を一度にテストできる様にまとめます。

import  junit.framework.*;
import  jp.co.itd.junit_sample.*;
public class TestJunitSample extends TestSuite
{
    public TestJunitSample (String name) { Super(name); }
    public static TestSuite suite() {
        TestSuite testSuite = new TestJunitSample ("JUnit Test Sample");
        testSuite.addTest(new TestConstructor ("Constructor test"));
        testSuite.addTest(new TestAccessor ("Accessor test"));
        testSuite.addTest(new TestOperator ("Operator test"));
        testSuite.addTest(new TestException ("Exception test"));
        return testSuite;
    }
}

④ テスト実行

 今作成したテスト用プログラムをコンパイルして実行してみます。
 メソッド内部は何も実装されていないので当然全てエラーとなる筈です。
 この最初にエラーになるのを確認するのがテストファーストの流儀。


⑤ メソッドの実装とテストの繰り返し

 手順4で報告されたエラーに対し、1件ずつ機能を実装させて解消します。最終的にエラーが報告されなくなればコーディング完了です。
 この時、まとめてコーディングを済ませてからテストを実行するのではなく、1つ実装してはテストを実行するという具合にできるだけ短い間隔でテストを繰り返すのがコツ
 今まで正常に終了していたテストケースが急にエラーになったら、前回テスト実行後変更したところに原因があります。テスト間隔が短いほど範囲が限定されバグの特定が容易になります。
 このように常にデグレードを監視していれば、品質は格段に向上します。

アジャイルプロセスの導入について
第1回 XPの概要
第2回 XPの光と影
第3回 テストファーストの考え方
第4回 リファクタリングの考え方
参考文献
ページトップ
のみほーだい!TOP

トラストサービス ITソリューション事業部
Copyright(C) Trust Service Co.,Ltd. All Rights Reserved.