本文算是一個(gè)關(guān)于Junit4相關(guān)的知識分享,但是不同于網(wǎng)上大段的源碼分析,模式學(xué)習(xí)文章,我想通過問答的形式,引出代碼來簡明闡述JUnit4是如何實(shí)現(xiàn)需要的功能的。
考慮到任何一個(gè)框架,都是為了解決問題而存在的。那么我想,帶著問題去看源碼會不會事半功倍呢?
Note:本文基于Junit4.11源碼
Junit4怎么能跑Case?
眾所周知,JUnit框架能幫助跑單元測試的Case,那么它到底是如何實(shí)現(xiàn)的呢?換句話說,如果沒有JUnit,我們會怎么執(zhí)行Case?
OK,很簡單,一個(gè)case是一個(gè)方法,那要想執(zhí)行他們,直接在Main方法里調(diào)用可以了。
但是當(dāng)Case很多時(shí),我們得一個(gè)一個(gè)在Main方法里顯示的調(diào)用Case。
雖然稍顯繁瑣,還是可以解決問題的,不過拓展性不敢恭維了,別人也沒辦法使用我們已寫的東西?!
那么,如果要上升到框架層面,能夠讓更多的人使用,該怎么做呢? Junit給了我們很好的例子.
好的單元測試框架一定是大限度的方便使用者,讓其盡可能只關(guān)注單元測試本身,而不是其他一些冗余的事情.
很明顯Junit做到了這一點(diǎn), 它不需要你去顯示的自己調(diào)用Case,一切都幫你做好,你只要告訴它要測哪個(gè)類好了,以JUnit4.11默認(rèn)的Runner為例:
BlockJUnit4ClassRunner aRunner = new BlockJUnit4ClassRunner(JunitTest.class);
aRunner.run(new RunNotifier());
Debug進(jìn)去,我們會發(fā)現(xiàn),原來JUnit是用反射機(jī)制來跑我們寫的Case。
public class InvokeMethod extends Statement {
private final FrameworkMethod fTestMethod;
private Object fTarget;
public InvokeMethod(FrameworkMethod testMethod, Object target) {
fTestMethod = testMethod;
fTarget = target;
}
@Override
public void evaluate() throws Throwable {
fTestMethod.invokeExplosively(fTarget);
}
}
這里的InvokeMethod是實(shí)際執(zhí)行方法的類,跟進(jìn)去方法:fTestMethod.invokeExplosively(fTarget);
我們會看到Method.invoke(obj,args)的終反射調(diào)用。
public Object invokeExplosively(final Object target, final Object... params)
throws Throwable {
return new ReflectiveCallable() {
@Override
protected Object runReflectiveCall() throws Throwable {
return fMethod.invoke(target, params);
}
}.run();
}
如何實(shí)現(xiàn) @Test 標(biāo)記功能?
Junit在4.0以后進(jìn)行了一個(gè)大的版本變動(dòng),引入了Java 1.5的注解功能,像我們常用的 @Test, @BeforeClass, @AfterClass 一樣,那么這些功能是如何實(shí)現(xiàn)的呢?
以 @Test 為例:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Test {
static class None extends Throwable {
private static final long serialVersionUID = 1L;
private None() {
}
}
Class<? extends Throwable> expected() default None.class;
long timeout() default 0L;
}
它以RetentionPolicy.RUNTIME標(biāo)記, 表示這個(gè)注解在運(yùn)行時(shí)仍然會被JVM保存,因此可以通過反射得到它:
protected List<FrameworkMethod> computeTestMethods() {
return getTestClass().getAnnotatedMethods(Test.class);
}
這樣JUnit得到了所有你要跑的Case。
而對于 @BeforeClass, @AfterClass 都是一個(gè)原理。
Junit4如何判斷結(jié)果?
上面說到如何找Case,如何跑Case,那么對于一個(gè)完成的執(zhí)行流程,我們還缺一塊:你的Case是Pass還是Fail?
比如下面:
org.junit.Assert.assertTrue("check assert method", false);
我們通常是用Assert去做斷言的,還有其他的方法,比如assertEqulas,assertNotEqulas。顯而易見,上面的斷言結(jié)果一定是Fail的,那具體是如何實(shí)現(xiàn)的呢?
要解開謎題,我們先看看Assert.assertTrue(String message, boolean condition)方法的具體實(shí)現(xiàn):
static public void assertTrue(String message, boolean condition) {
if (!condition) {
fail(message);
}
}
static public void fail(String message) {
if (message == null) {
throw new AssertionError();
}
throw new AssertionError(message);
}
當(dāng)condition是false時(shí),它會拋一個(gè)java.lang.AssertionError異常。這個(gè)類是從Java 1.4引入的,并且它跟所有的Java的異常一樣都是從Throwable繼承來的。
那Junit如何捕獲呢?
在org.junit.runners.ParentRunner<T>類我們找到了答案:
protected final void runLeaf(Statement statement, Description description,
RunNotifier notifier) {
EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
eachNotifier.fireTestStarted();
try {
statement.evaluate();
} catch (AssumptionViolatedException e) {
eachNotifier.addFailedAssumption(e);
} catch (Throwable e) {
eachNotifier.addFailure(e);
} finally {
eachNotifier.fireTestFinished();
}
}
這個(gè)方法目的是執(zhí)行單個(gè)Case,它是用Try Catch來捕獲所有非預(yù)期的異常錯(cuò)誤。
只要Catch到Throwable異常了,很明顯Case掛了。反之,Case則是pass的。
這里還可以繼續(xù)深入,比如我們知道,@Test 有一個(gè)expected參數(shù),可以讓我們填入期望捕獲的異常,那么JUnit如何實(shí)現(xiàn)的呢?
總結(jié)
好了,我們來看看我們都說了啥:
Junit通過 @Test 注解,找到我們想跑的Case
通過反射,來執(zhí)行我們的Case
后通過Catch Throwable exception來判斷我們的case 是Pass還是Fail
很明顯JUnit還有很多其他的功能,比如TestSuite,Rule,各種Runners,這里不在一一羅列了。
我想,作為表達(dá)一條Case執(zhí)行的主線,講到這里應(yīng)該已經(jīng)足夠了,不是嘛?