やぼったい開発 > 単体試験項目のコーディング

単体試験項目のコーディング


UT試験の自動化の first step.

単体テストフレームワークの書き方

3つのメソッドタイプのUT試験を説明しつつ、JUnitの作成方法と Mockでのシンプルな試験方法を理解することが目的!
差が明確で理解しやすいようにシンプルで最小限の書き方をしてます。



説明フロー(A,B,Cの3パターンを順に説明します)

    A.最も単純なパターン
  1. 簡単な単体テスト
  2. JUnitで書き換えると

    B.事前処理のいるパターン
  3. 準備のいる簡単な単体テスト
  4. また、JUnitで書き換えると

    C.モックパターン
  5. モックを使用するテスト(テスト対象メソッドがモックを呼ぶ場合のテスト)
#ソースは最後につけました。
解説 対応ソース
1.簡単な単体テスト
Calc.java(↓) の sum()メソッドをテストする場合をこのページでは考えてみます。

実装本体としてメソッドは以下の振る舞いを期待している。
・2つの引数を受け合計したものを返す。


テストとしては
・2つの引数を渡し、正しく値が返ってきていることを確認する。

Calc.java
public class Calc {
    //1.簡単な単体テスト
    public static int sum(int num1, int num2)
    {
        return(num1+num2);
    }
}
Test_Calc_Normal.java

//普通のテストケース
public class Test_Calc_Normal
{
    public static void main(String[] arguments)
    {
        Test_Calc_Normal tester = new Test_Calc_Normal();

        //1.簡単な単体テスト
        tester.test_sum();
    }

    //1.簡単な単体テスト
    public void test_sum()
    {
        int result = Calc.sum(20,30);//直値を記載すること
        if (result != 50)//直値を記載すること
        {
             System.out.println("間違い"+result);
        }
    }
}
2.JUnitで書き換えると
これをJUnitで記載すると→

Test_Calc_JUnit.java#test_sum()の様に記載する。

#importもしているよ。
Test_Calc_JUnit.java

import junit.framework.TestCase;

//JUnit フレームワークのテストケース
public class Test_Calc_JUnit extends TestCase
{
    //1.簡単な単体テスト
    //2.JUnitで置き換えてテスト
    public void test_sum()
    {
        int result = Calc.sum(20,30);//直値を記載すること
        assertEquals(50,result,0);//直値を記載すること
    }
}
3.準備のいる簡単な単体テスト
今度は Calc.java の operand()メソッドをテストする場合を考える。 (ファイルを作成しておいてからテストするケースや、 DBやデータを更新してからテストするように準備する場合)
期待している振る舞いとテスト観点は 1.と同様なので省略。 Calc.java public class Calc { //3.準備のいる簡単な単体テスト //4.JUnitで置き換えてテスト public int operandType = 0; public int operand(int num1, int num2) { if (operandType == 0) { return(num1+num2); } else { return(num1-num2); } } }
Test_Calc_Normal.java

//普通のテストケース
public class Test_Calc_Normal
{
    public static void main(String[] arguments)
    {
        Test_Calc_Normal tester = new Test_Calc_Normal();

        //1.簡単な単体テスト
        tester.test_sum();


        //3.準備のいる簡単な単体テスト
        tester.test_operand_minus();

    }

    //1.簡単な単体テスト
             :

    //3.準備のいる簡単な単体テスト
    public void test_operand_minus()
    {
        //準備
        Calc c = new Calc();
        c.operandType = 1;

        //テスト
        int result = c.operand(40,30);//直値を記載すること

        //確認
        if (result != 10)//直値を記載すること
        {
             System.out.println("間違い"+result);
        }
    }
}
4.JUnitで書き換えると
これをJUnitで記載すると
Test_Calc_JUnit.java#test_operand_minus()の様に記載する。
Test_Calc_JUnit.java

import junit.framework.TestCase;

//JUnit フレームワークのテストケース
public class Test_Calc_JUnit extends TestCase
{
    //1.簡単な単体テスト
    //2.JUnitで置き換えてテスト
               :

    //3.準備のいる簡単な単体テスト
    //4.JUnitで置き換えてテスト
    public void test_operand_minus()
    {
      //準備
        Calc c = new Calc();
        c.operandType = 1;  //テスト前準備(例なのでシンプルすぎですが)

        //テスト
        int result = c.operand(40,30);//直値を記載すること

        assertEquals(10,result,0);//直値を記載すること

    }
    //もちろん operandType = 0のパターンも行う。

}
5.モックを使用するテスト(テスト対象メソッドがモックを呼ぶ場合のテスト)
今度は Calc.java の operate()メソッドをテストする場合を考える。

テスト対象メソッドの特徴としては、他のクラスで定義されているメソッド呼び出しが入る。

テスト観点 ( operate() メソッド)
・Operatorクラスの operateに int型引数で 2つ入力されること。
・operateメソッドからの返り値を返していること。
#要するに Calc.operator()をテストするので、 operator.operate(3,8)と正しく呼ばれていることを確認します。その呼ばれていることを mock側からから確認します。
ここで Operator のようにinterface を使うことをせずに実装すると密結合してしまい、メソッドのみのテストができなくなってしまうので注意。

テスト実装方針としては
Operator のインタフェースを使用したモックを利用し、その実態の中で、テストしやすい環境を作る。


テストは
Test_Calc_Normal.java test_operator()
モック定義は
Test_Calc_Normal.java のInterface 定義 OperatorMock_Operator_24を参照


テスト(test_operator() ) の流れ
a)テスト対象クラス(Calc)インスタンスを作成
b)対象メソッドから呼ばれるメソッドのためのモックオブジェクトを作成。
c)テストメソッドを呼ぶ。
d) テストメソッドから、モックが呼ばれる。
e) 各処理が終わり、値が設定される。
f)状態を確認する。


Calc.java

public class Calc
{

    //5.テスト対象メソッドがモックを呼ぶ場合のテスト
    Operator operator;

    public int operator()
    {
        //e) 各処理が終わり、値が設定される。
        int value = 
          //d)テストメソッドから、モックが呼ばれる。
          operator.operate(3,8);
        return value;
    }

}

Operator.java

interface Operator
{
    public int operate(int num1, int num2);
}

class OperatorConcreate implements Operator
{
    public int operate(int num1, int num2)
    {
        return (num1 * num2);
    }
}
//今回は テスト対象でないため、このクラス(OperatorConcreate)はテストされない。
Test_Calc_Normal.java

//普通のテストケース
public class Test_Calc_Normal
{
    public static void main(String[] arguments)
    {
        Test_Calc_Normal tester = new Test_Calc_Normal();

        //1.簡単な単体テスト
        tester.test_sum();


        //3.準備のいる簡単な単体テスト
        tester.test_operand_minus();


        //5.モックを使用するテスト
        tester.test_operator();
    }

    //1.簡単な単体テスト


    //3.準備のいる簡単な単体テスト


    //5.モックを使用するテスト(テスト対象メソッドがモックを呼ぶ場合のテスト)
    public void test_operator()
    {
        //準備
        //a)テスト対象クラスインスタンス作成
        Calc c = new Calc();
        //b)対象メソッドから呼ばれるメソッドのためのモックオブジェクトを作成
        OperatorMock.Operator_24 mock
          = new OperatorMock_Operator_24();
        c.operator = (Operator)mock;

        //テスト
        //c)テスト対象メソッドを呼ぶ
        int result = c.operator();

        //result = 24の確認は省略

        //確認
        //f)状態を確認する。
        if (! mock.calledState.equals("1: 3  /2: 8"))
        {
             System.out.println("間違い"+result+"予定:1: 3  /2: 8");
        }


    }


}

//5.モックを使用するテスト(テスト対象メソッドがモックを呼ぶ場合のテスト)
// mock 定義
class OperatorMock_Operator_24 implements Operator
{
    //d)テストメソッドから、モックが呼ばれる。
    String calledState = "";//状態確認用 mockは実ソースでないなのでガンガンテストしやすいように改修できる。
    //逆にinterfaceでない場合、テストしようとすると具象クラス(OperatorConcreate)か実クラス(Calc)の改修が入ってしまうが、
    //UTの観点で、商用に出すソースと変わってしまうのでNG。ソース管理の問題とか絡んでくるし・・

    public int operate(int num1 , int num2)
    {
        calledState = calledState +"1: " + num1 + "  /2: " + num2;
        return 24;//モックなので単純に値を返すだけ
    }
}


モックの特徴を振り返る。
A)interfaceで間接的に他のメソッドが呼ばれている必要がある。
B)interface で定義したMockオブジェクトをその都度作る必要がある。
C)モックオブジェクトの数も test case に合わせて膨大になる。
D)メソッド呼び出しが一つ増えただけで、テストが大変になる。



テストが複雑にならないように、そしてモックの数が膨大にならないように、メソッドの設計に気をつける必要がある。
フロー部と処理部を分けるなどするとテスト作業が軽減する。


ソース

Calc.java

public class Calc
{
    //1.簡単な単体テスト
    //2.JUnitで置き換えてテスト
    public static int sum(int num1, int num2)
    {
        return(num1+num2);
    } 


    //3.準備のいる単体テスト
    //4.JUnitで置き換えてテスト
    public int operandType = 0;

    public int operand(int num1, int num2)
    {
        if (operandType == 0)
        {
            return(num1+num2);
        }
        else
        {
            return(num1-num2);
        }
    } 


    //5.テスト対象メソッドがモックを呼ぶ場合のテスト
    Operator operator;

    public int operator()
    {
        //e) 各処理が終わり、値が設定される。
        int value = operator.operate(3,8);
        return value;
    }

}


Operator.java

interface Operator
{
    public int operate(int num1, int num2);
}

class OperatorConcreate implements Operator
{
    public int operate(int num1, int num2)
    {
        return (num1 * num2);
    }
}
Test_Calc_Normal.java

//普通のテストケース
public class Test_Calc_Normal
{
    public static void main(String[] arguments)
    {
        Test_Calc_Normal tester = new Test_Calc_Normal();

        //1.簡単な単体テスト
        tester.test_sum();


        //3.準備のいる簡単な単体テスト
        tester.test_operand_minus();


        //5.モックを使用するテスト
        tester.test_operator();
    }

    //1.簡単な単体テスト
    public void test_sum()
    {
        int result = Calc.sum(20,30);//直値を記載すること
        if (result != 50)//直値を記載すること
        {
             System.out.println("間違い"+result);
        }
    }

    //3.準備のいる簡単な単体テスト
    public void test_sum()
    {
        int result = Calc.sum(20,30);//直値を記載すること
        if (result != 50)//直値を記載すること
        {
             System.out.println("間違い"+result);
        }
    }

    //5.モックを使用するテスト(テスト対象メソッドがモックを呼ぶ場合のテスト)
    public void test_operator()
    {
        //準備
        //a)テスト対象クラスインスタンス作成
        Calc c = new Calc();
        //b)対象メソッドから呼ばれるメソッドのためのモックオブジェクトを作成
        OperatorMock.Operator_24 mock
          = new OperatorMock_Operator_24();
        c.operator = (Operator)mock;

        //テスト
        //テスト対象メソッドを呼ぶ
        int result = c.operator();//直値を記載すること

        //確認
        //f)状態を確認する。
        if (! mock.calledState.equals("1: 3  /2: 8"))
        {
             System.out.println("間違い"+result+"予定:1: 3  /2: 8");
        }
    }
}

//5.モックを使用するテスト(テスト対象メソッドがモックを呼ぶ場合のテスト)
// mock 定義
class OperatorMock_Operator_24 implements Operator
{
    //e) 各処理が終わり、値が設定される。
    String calledState = "";
    public int operate(int num1 , int num2)
    {
        calledState = calledState +"1: " + num1 + "  /2: " + num2;
        return 24;
    }
}
Test_Calc_JUnit.java

import junit.framework.TestCase;

//JUnit フレームワークのテストケース
public class Test_Calc_JUnit extends TestCase
{
    //1.簡単な単体テスト
    //2.JUnitで置き換えてテスト
    public void test_sum()
    {
        int result = Calc.sum(20,30);//直値を記載すること
        assertEquals(50,result,0);//直値を記載すること
    }

    //3.簡単な単体テスト
    //4.JUnitで置き換えてテスト
    public void test_operand_minus()
    {
        //準備
        Calc c = new Calc();
        c.operandType = 1;

        //テスト
        int result = c.operand(40,30);//直値を記載すること

        assertEquals(10,result,0);//直値を記載すること

    }
    //もちろん operandType = 0のパターンも行う。

}





http://www2.ocn.ne.jp/~cheerful/UT/
since (05/03/2006)
ご意見感想文句などどうぞ santarou@sepia.ocn.ne.jp
http://www2.ocn.ne.jp/~cheerful/script/