單元測驗是每個程式員必備的技能,而Runner是每個單元測驗類必有屬性,本文通過解讀Junit原始碼,介紹junit中每個執行器的使用方法,讓讀者在單元測驗時,可以靈活的使用Runner執行器,
一、背景
在今年的敏捷團隊建設中,京東物流通過Suite執行器實作了一鍵自動化單元測驗,Juint除了Suite執行器還有哪些執行器呢?由此京東物流的Runner探索之旅開始了!
二、RunWith
RunWith的注釋是當一個類用@RunWith注釋或擴展一個用@RunWith注釋的類時,JUnit將呼叫它參考的類來運行該類中的測驗,而不是內置到JUnit中的運行器,就是測驗類根據指定運行方式進行運行,
代碼如下:
public @interface RunWith {
Class<? extends Runner> value();
}
其中:Runner 就是指定的運行方式,
三、Runner
Runner的作用是告訴Junit如何運行一個測驗類,它是一個抽象類,通過RunWith 指定具體的實作類,如果不指定默認使用BlockJUnit4ClassRunner,Runner的代碼如下:
public abstract class Runner implements Describable {
public abstract Description getDescription();
public abstract void run(RunNotifier notifier);
public int testCount() {
return getDescription().testCount();
}
}
3.1 ParentRunner
ParentRunner是一個抽象類,提供了大多數特定于運行器的功能,是經常使用運行器的父節點,實作了Filterable,Sortable介面,可以過濾和排序子物件,
提供了3個抽象方法:
protected abstract List<T> getChildren();
protected abstract Description describeChild(T child);
protected abstract void runChild(T child, RunNotifier notifier);
3.1.1 BlockJUnit4ClassRunner
BlockJUnit4ClassRunner是Juint4默認的運行器,具有與舊的測驗類運行器(JUnit4ClassRunner)完全相同的行為,
ParentRunner3個抽象方法的實作如下:
@Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
Description description = describeChild(method);
if (isIgnored(method)) {
notifier.fireTestIgnored(description);
} else {
runLeaf(methodBlock(method), description, notifier);
}
}
@Override
protected Description describeChild(FrameworkMethod method) {
Description description = methodDescriptions.get(method);
if (description == null) {
description = Description.createTestDescription(getTestClass().getJavaClass(),
testName(method), method.getAnnotations());
methodDescriptions.putIfAbsent(method, description);
}
return description;
}
@Override
protected List<FrameworkMethod> getChildren() {
return computeTestMethods();
}
runChild() :
-
呼叫describeChild()
-
判斷方法是否包含@Ignore注解,有就觸發TestIgnored事件通知
-
構造Statement回呼,通過methodBlock()構造并裝飾測驗方法
-
執行測驗方法呼叫statement.evaluate()
describeChild() : 對測驗方法創建Description并進行快取
getChildren():回傳運行測驗的方法, 默認實作回傳該類和超類上所有用@Test標注的未重寫的方法
3.1.2 BlockJUnit4ClassRunnerWithParameters
BlockJUnit4ClassRunnerWithParameters是一個支持引數的BlockJUnit4ClassRunner,引數可以通過建構式注入或注入到帶注釋的欄位中,引數包含名稱、測驗類和一組引數,
private final Object[] parameters;
private final String name;
public BlockJUnit4ClassRunnerWithParameters(TestWithParameters test)
throws InitializationError {
super(test.getTestClass().getJavaClass());
parameters = test.getParameters().toArray(
new Object[test.getParameters().size()]);
name = test.getName();
}
引數代碼如下:
public class TestWithParameters {
private final String name;
private final TestClass testClass;
private final List<Object> parameters;
public TestWithParameters(String name, TestClass testClass,
List<Object> parameters) {
notNull(name, "The name is missing.");
notNull(testClass, "The test class is missing.");
notNull(parameters, "The parameters are missing.");
this.name = name;
this.testClass = testClass;
this.parameters = unmodifiableList(new ArrayList<Object>(parameters));
}
BlockJUnit4ClassRunnerWithParameters一般結合Parameterized使用
3.1.3 Theories
Theories允許對無限資料點集的子集測驗某種功能,提供一組引數的排列組合值作為待測方法的輸入引數,同時注意到在使用Theories這個Runner的時候,待測方法可以擁有輸入引數,可以使您的測驗更加靈活,
測驗代碼如下:
@RunWith(Theories.class)
public class TheoriesTest {
@DataPoints
public static String[] tables = {"方桌子", "圓桌子"};
@DataPoints
public static int[] counts = {4,6,8};
@Theory
public void testMethod(String table, int count){
System.out.println(String.format("一套桌椅有一個%s和%d個椅子", table, count));
}
}
運行結果:
圖2 Theories測驗代碼的執行結果
3.1.4 JUnit4
JUnit4是Junit4默認執行器的別名,想要顯式地將一個類標記為JUnit4類,應該使用@RunWith(JUnit4.class),而不是,使用@RunWith(BlockJUnit4ClassRunner.class)
3.1.5 Suite
Suite允許您手動構建包含來自許多類的測驗的套件.通過Suite.SuiteClasses定義要執行的測驗類,一鍵執行所有的測驗類,
測驗代碼如下:
@RunWith(Suite.class)
@Suite.SuiteClasses({Suite_test_a.class,Suite_test_b.class,Suite_test_c.class })
public class Suite_main {
}
public class Suite_test_a {
@Test
public void testRun(){
System.out.println("Suite_test_a_running");
}
}
public class Suite_test_b {
@Test
public void testRun(){
System.out.println("Suite_test_b_running");
}
}
public class Suite_test_c {
@Test
public void testRun(){
System.out.println("Suite_test_c_running");
}
}
執行結果:
圖3 Suite測驗代碼的執行結果
如結果所示:執行MainSuit時依次執行了Suite_test_a,Suite_test_b,Suite_test_c 的方法,實作了一鍵執行,
3.1.6 Categories
Categories在給定的一組測驗類中,只運行用帶有@ inclecategory標注的類別或該類別的子型別標注的類和方法,通過ExcludeCategory過濾型別,
測驗代碼如下:
public interface BlackCategory {}
public interface WhiteCategory {}
public class Categories_test_a {
@Test
@Category(BlackCategory.class)
public void testFirst(){
System.out.println("Categories_test_a_testFirst_running");
}
@Test
@Category(WhiteCategory.class)
public void testSecond(){
System.out.println("Categories_test_a_testSecond_running");
}
}
public class Categories_test_b {
@Test
@Category(WhiteCategory.class)
public void testFirst(){
System.out.println("Categories_test_b_testFirst_running");
}
@Test
@Category(BlackCategory.class)
public void testSecond(){
System.out.println("Categories_test_b_testSecond_running");
}
}
執行帶WhiteCategory的方法
@RunWith(Categories.class)
@Categories.IncludeCategory(WhiteCategory.class)
@Categories.ExcludeCategory(BlackCategory.class)
@Suite.SuiteClasses( { Categories_test_a.class, Categories_test_b.class })
public class Categories_main {
}
運行結果:
圖4 Categories測驗代碼WhiteCategory分組執行結果
執行帶BlackCategory的方法
@RunWith(Categories.class)
@Categories.IncludeCategory(BlackCategory.class)
@Categories.ExcludeCategory(WhiteCategory.class)
@Suite.SuiteClasses( { Categories_test_a.class, Categories_test_b.class })
public class Categories_main {
}
運行結果:
圖5 Categories測驗代碼BlackCategory分組執行結果
如運行結果所示,通過IncludeCategory,ExcludeCategory可以靈活的運行具體的測驗類和方法,
3.1.7 Enclosed
Enclosed使用Enclosed運行外部類,內部類中的測驗將被運行, 您可以將測驗放在內部類中,以便對它們進行分組或共享常量,
測驗代碼:
public class EnclosedTest {
@Test
public void runOutMethou(){
System.out.println("EnclosedTest_runOutMethou_running");
}
public static class EnclosedInnerTest {
@Test
public void runInMethou(){
System.out.println("EnclosedInnerTest_runInMethou_running");
}
}
}
運行結果:沒有執行內部類的測驗方法,
圖6 Enclosed測驗代碼的執行結果
使用Enclosed執行器:
@RunWith(Enclosed.class)
public class EnclosedTest {
@Test
public void runOutMethou(){
System.out.println("EnclosedTest_runOutMethou_running");
}
public static class EnclosedInnerTest {
@Test
public void runInMethou(){
System.out.println("EnclosedInnerTest_runInMethou_running");
}
}
}
執行結果:執行了內部類的測驗方法,
圖7 Enclosed測驗代碼的執行結果
3.1.8 Parameterized
Parameterized實作引數化測驗, 運行引數化的測驗類時,會為測驗方法和測驗資料元素的交叉乘積創建實體,
Parameterized包含一個提供資料的方法,這個方法必須增加Parameters注解,并且這個方法必
須是靜態static的,并且回傳一個集合Collection,Collection中的值長度必須相等,
測驗代碼:
@RunWith(Parameterized.class)
public class ParameterizedTest {
@Parameterized.Parameters
public static Collection<Object[]> initData(){
return Arrays.asList(new Object[][]{
{"小白",1,"雞腿"},{"小黑",2,"面包"},{"小紅",1,"蘋果"}
});
}
private String name;
private int count;
private String food;
public ParameterizedTest(String name, int count, String food) {
this.name = name;
this.count = count;
this.food = food;
}
@Test
public void eated(){
System.out.println(String.format("%s中午吃了%d個%s",name,count,food));
}
}
運行結果:
圖8 Parameterized測驗代碼的執行結果
3.2 JUnit38ClassRunner
JUnit38ClassRunner及其子類是Junit4的內部運行器,有一個內部類OldTestClassAdaptingListener
實作了TestListener介面,
3.3 ErrorReportingRunner
ErrorReportingRunner也是Junit4運行錯誤時拋出的例外,代碼如下:
private final List<Throwable> causes;
public ErrorReportingRunner(Class<?> testClass, Throwable cause) {
if (testClass == null) {
throw new NullPointerException("Test class cannot be null");
}
this.testClass = testClass;
causes = getCauses(cause);
}
private List<Throwable> getCauses(Throwable cause) {
if (cause instanceof InvocationTargetException) {
return getCauses(cause.getCause());
}
if (cause instanceof InitializationError) {
return ((InitializationError) cause).getCauses();
}
if (cause instanceof org.junit.internal.runners.InitializationError) {
return ((org.junit.internal.runners.InitializationError) cause)
.getCauses();
}
return Arrays.asList(cause);
}
當junit運行錯誤時,會拋出ErrorReportingRunner,例如:
public Runner getRunner() {
try {
Runner runner = request.getRunner();
fFilter.apply(runner);
return runner;
} catch (NoTestsRemainException e) {
return new ErrorReportingRunner(Filter.class, new Exception(String
.format("No tests found matching %s from %s", fFilter
.describe(), request.toString())));
}
}
3.4 IgnoredClassRunner
IgnoredClassRunner是當測驗的方法包含Ignore注解時,會忽略該方法,
public class IgnoredClassRunner extends Runner {
private final Class<?> clazz;
public IgnoredClassRunner(Class<?> testClass) {
clazz = testClass;
}
@Override
public void run(RunNotifier notifier) {
notifier.fireTestIgnored(getDescription());
}
@Override
public Description getDescription() {
return Description.createSuiteDescription(clazz);
}
}
IgnoredClassRunner的使用
public class IgnoredBuilder extends RunnerBuilder {
@Override
public Runner runnerForClass(Class<?> testClass) {
if (testClass.getAnnotation(Ignore.class) != null) {
return new IgnoredClassRunner(testClass);
}
return null;
}
}
當測驗時想忽略某些方法時,可以通過繼承IgnoredClassRunner增加特定注解實作,
四、小結
Runner探索之旅結束了,可是單元測驗之路才剛剛開始,不同的Runner組合,讓單元測驗更加靈活,測驗場景更加豐富,更好的實作了測驗驅動開發,讓系統更加牢固可靠,
作者:京東物流 陳昌浩
來源:京東云開發者社區
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/555100.html
標籤:其他
上一篇:業務程式員不建議造輪子
下一篇:返回列表
