最近準備練習使用 OpenRules 這套 business decision management system,因此同事找題目讓大家練習:How are ABO alleles inherited by our children? 答案請參考
http://www.bloodbook.com/inherited.html。
Q: 如何用程式表達答案呢?
A: 根據前輩的教誨,我們要對 interface 寫程式,不要對 class 寫程式。因此這個問題的 interface 如下:
package main;
import java.util.Set;
public interface BloodTypeCalculator {
Set<BloodType> calculate(BloodType father, BloodType mother);
Set<BloodType> calculate(BloodType grandfather,
BloodType grandmother,
BloodType maternalGrandfather,
BloodType maternalGrandmother);
}
BloodType 是簡單的 Java enum type: O, A, B, AB.
BloodTypeCalculator 提供兩個 methods:
- 根據父母的血型計算小孩可能的血型
- 根據父母的父母的血型計算小孩可能的血型
好,那要怎麼測
BloodTypeCalculator interface? 這個問題非常難回答,我的想法是:
直接寫 test class,讓 ctor 可以接收 BloodTypeCalculator interface pointer (in the sense of C++). 這樣就可以了。
但這樣不足以成事,如果沒有適當設計 test class,JUnit 不支援吃參數的 ctor。解決辦法就是
parameterized test. 不囉唆,直接貼程式碼:
package test;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import junit.framework.Assert;
import main.BloodType;
import main.BloodTypeCalculator;
import main.NativeBloodTypeCalculator;
import main.OpenRulesBloodTypeCalculator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(value = Parameterized.class)
public class BloodTypeCalculatorTest {
public BloodTypeCalculatorTest(BloodTypeCalculator c) {
this.c = c;
}
@Parameters
public static Collection<Object[]> parameters() {
Object[][] result = new Object[][] {
{ new NativeBloodTypeCalculator() },
{ new OpenRulesBloodTypeCalculator() }
};
return Arrays.asList(result);
}
@Test
public void calculate_OAndA_returnsOAndA() throws Exception {
Set<BloodType> expected = new HashSet<BloodType>();
expected.add(BloodType.O);
expected.add(BloodType.A);
Set<BloodType> actual = c.calculate(BloodType.O, BloodType.A);
Assert.assertEquals(expected, actual);
// It's OK when we exchange father's blood type and mother's one.
actual = c.calculate(BloodType.A, BloodType.O);
Assert.assertEquals(expected, actual);
}
private BloodTypeCalculator c;
}
這邊寫法都很固定。首先要讓 JUnit 知道這是 parameterized test,因此前面要加 annotation
@RunWith(value = Parameterized.class).
接著寫
public static Collection<Object[]> parameters() method,前面一定要加
@Parameters annotation,此 method 一定要 static,return value type 一定要
Collection<Object[]>,
Collection<Object[][]> 等等之類的,Object 的 dimension 就是 number of parameters of ctor. 至於 method name,自己喜歡就好了。
parameter() method 內容請直接模仿上面寫法,保證不會錯。
當 JUnit 準備執行此 test class,她會先看 parameter() method,讀到 2 筆參數,就為這個 test class 生成 2 個 test class
objects. 接著再執行內部的 test methods.
因為 OpenRulesBloodTypeCalculator class 沒有 real implementation,所以全部 failure.
PS:
- JUnit 中,error 和 failure 代表不同的意義
- Test method 的 naming rule: 分三段命名。
- Method name: The name of the method you are testing.
- State under test: The conditions used to produce the expected behavior.
- Expected behavior: What you expect the tested method to do under the specified conditions.
參考書籍:Roy Osherove, The Art of Unit Testing: With Examples in .Net