安卓單元測試 (八):Junit Rule 的使用

小創發表於2017-03-16

JUnit Rule是什麼

一個JUnit Rule就是一個實現了TestRule的類,這些類的作用類似於@Before@After,是用來在每個測試方法的執行前後執行一些程式碼的一個方法。
如果你不清楚@Before@After這些Annotation的意思,Chances are你還不瞭解Junit的使用,建議先看這篇文章
那為什麼不直接用這些annotation呢?這是因為它們都只能作用於一個類,如果同一個setup需要在兩個類裡面同時使用,那麼你就要在兩個測試類裡面定義相同的@Before方法,然後裡面寫相同的程式碼,這就造成了程式碼重複。有的人說你可以用繼承啊,首先我想說,我很討厭繼承這個東西,所以如果可以不用繼承的話,我就不會用;再次我想說,如果你不討厭繼承的話,從現在開始,你也應該慢慢的討厭它了。
此外,JUnit Rule還能做一些@Before這些Annotation做不到的事情,那就是他們可以動態的獲取將要執行的測試類、測試方法的資訊。這個在接下來的一個例子裡面可以看到。

怎麼用JUnit Rule?

使用框架自帶的Rule

很多測試框架比如JUnit、Mockito自帶給我們很多已經實現過好了的JUnit Rule,我們可以直接拿來用。比如TimeoutTemporaryFolder,等等。這些Rule的使用方法非常簡單。定義一個這些類的public field,然後用@Rule修飾一下就好了。比如

public class ExampleTest {
    @Rule
    public Timeout timeout = new Timeout(1000);  //使用Timeout這個 Rule,

    @Test
    public void testMethod1() throws Exception {
        //your tests
    }

    @Test
    public void testMethod2() throws Exception {
        //your tests2
    }

    //other test methods
}複製程式碼

那麼,對於上面這個ExampleTest的每一個測試方法。它們的執行時間都不能超過1秒鐘,不然就會標誌為失敗。而它的實現方式就是在每個方法測試之前都會記錄一下時間戳,然後開始倒數計時,1秒鐘之後如果方法還沒有執行結束,就把結果標記為失敗。
這裡需要注意的一點是Rule需要是public field

實現自己的Rule

當然,如果只能用框架自帶的Rule,那功能未免太受限了,JUnit Rule最強大的地方在於,我們可以自己寫滿足我們自己需要的Rule。所以現在的問題是怎麼寫這個Rule。簡單來說,寫一個Rule就是implement一個TestRule interface,實現一個叫apply()的方法。這個方法需要返回一個Statement物件。下面給一個例子,這個 Rule的作用是,在測試方法執行之前,記錄測試方法所在的類名和方法名,然後在測試方法執行之後列印出來,至於怎麼在測試方法執行前後做這些事情,下面例子中的註釋裡面說的很清楚。

public class MethodNameExample implements TestRule {
    @Override
    public Statement apply(final Statement base, final Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                //想要在測試方法執行之前做一些事情,就在base.evaluate()之前做
                String className = description.getClassName();
                String methodName = description.getMethodName();

                base.evaluate();  //這其實就是執行測試方法

                //想要在測試方法執行之後做一些事情,就在base.evaluate()之後做
                System.out.println("Class name: "+className +", method name: "+methodName);
            }
        };
    }
}複製程式碼

這個Rule這樣就算寫好了,現在來試試,用這個 Rule的方法跟使用自帶的Rule的用法是一樣的,寫一個public field,用@Rule修飾一下就好了。

public class ExampleUnitTest {
    @Rule
    public MethodNameExample methodNameExample = new MethodNameExample();
    @Test
    public void addition_isCorrect() throws Exception {
        assertEquals(4, 2 + 2);
    }

    @Test
    public void mulitiplication_isCorrect() throws Exception {
        assertEquals(4, 2 * 2);
    }
}複製程式碼

執行結果如下:

安卓單元測試 (八):Junit Rule 的使用

在右邊的框框可以看到,把測試方法的方法名和所在的類名列印出來了。

上面的例子對於TestRule的實現應該說的比較清楚,但是看起來沒多大用。下面給另外的一個例子,大家或許會覺得這個東西更有用一點。在安卓裡面,我們經常在很多地方需要用到Context這個東西。我們的做法是將這個東西儲存在一個類裡面,作為靜態變數存在:

public class ContextHolder {
    private static Context sContext;

    public static void set(Context context) {
        sContext = context;
    }

    public static Context get() {
        return sContext;
    }
}複製程式碼

然後在自定義的Application#onCreate()裡面調一下ContextHolder.set()將這個context初始化。

如果你的當前專案是一個library,那麼你在測試環境下是沒有這個Application的,Robolectric會給你造一個Application,放在RuntimeEnvironment.application裡面。所以在測試環境下你可以使用這個instance來將ContextHolder初始化。在這種情況下,你就可以用一個Rule來實現這樣的效果,在用到Context的測試方法執行之前將ContextHolder初始化:

public class ContextRule implements TestRule {
    @Override
    public Statement apply(Statement base, Description description) {
        ContextHolder.set(RuntimeEnvironment.application);
        return base;
    }
}複製程式碼

這樣,在執行一些用到context的測試方法之前,你就可以使用這個Rule來給Context賦值了。

其實,最常用的Rule之一就是結合@Mock之類的Annotation快速的建立mock,但是這點我想作為一篇單獨的文章寫一下。原因之一是因為它涉及到的東西不僅僅是Rule,還有其它的一些東西。更重要的原因是,我希望大家能知道這個東西,而不是被這篇文章淹沒。請關注下一篇文章吧!

小結

JUnit Rule的介紹就到這裡,應該說比較簡單,卻是非常有幫助。希望這篇文章能幫助到大家瞭解這個東西。
這篇文章的程式碼放在這個github repo裡面。

獲取最新文章或想加入安卓單元測試交流群,請關注下方公眾號

安卓單元測試 (八):Junit Rule 的使用

相關文章