github: junit-extension
背景
原生的Junit無法滿足我們在自動化測試實(shí)踐過程中的碰到一些需求,比如
- 管理人員希望統(tǒng)計測試組每個成員開發(fā)的用例數(shù)目, 要是每個用例都能夠有注釋
- 在本地調(diào)試測試用例時,測試工程師希望只運(yùn)行于自己開發(fā)的用例
- 系統(tǒng)測試時需要執(zhí)行全量用例,回歸測試時希望跑High優(yōu)先級的用例
- 測試用例運(yùn)行失敗后能夠有自動重試的機(jī)制
- junit產(chǎn)生的測試報告希望與內(nèi)部系統(tǒng)集成,有一些定制需求
- ......
注解
-
@Author注解測試用例的作者 -
@Priority注解測試用例的優(yōu)先級,有High.class,Middle.class,Low.class可選 -
@Comment注解測試用例的注釋
以上三種注解和Junit build-in的注解@Test配合使用,示例如下
import org.junit.Test;
import org.sdet.junit.extension.TestBase;
import org.sdet.junit.extension.annotation.Author;
import org.sdet.junit.extension.annotation.High;
import org.sdet.junit.extension.annotation.Middle;
import org.sdet.junit.extension.annotation.Priority;
import static org.junit.Assert.assertEquals;
public class HelloJUnitExtension extends TestBase {
// 該測試用例的作者是michaelyan,優(yōu)先級是Middle
@Test
@Author("michaelyan")
@Priority(Middle.class)
public void shouldFail() {
assertEquals(1, 0);
}
......
}
-
@AuthorFilter注解目標(biāo)測試用例的作者, -
@PriorityFilter注解目標(biāo)測試用例的優(yōu)先級
以上兩種注解用來注解自定義的Runner
//
@RunWith(CustomSuite.class)
@AuthorFilter({"michaeljyan"})
@PriorityFilter({High.class, Middle.class})
@Suite.SuiteClasses({HelloJUnitExtension.class})
public class MyRunner {}
測試用例加載邏輯 CustomSuite
類CustomSuite的方法run(RunNotifier notifier)與基類ParentRunner<T>的實(shí)現(xiàn)比,只多了一行代碼。如果沒有這一行,RunListener的回調(diào)函數(shù)public void testRunStarted(Description description)不會執(zhí)行。
// 多出的一行代碼
notifier.fireTestRunStarted(description);
測試用例過濾邏輯 CustomFilter
詳情請參考下面3個API實(shí)現(xiàn)
@Override
public boolean shouldRun(Description description) {}
private boolean shouldfilterByAuthor(Description description) {}
private boolean shouldfilterByPriority(Description description) {}
失敗重試
JUnit中測試用例運(yùn)行是通過ParentRunner<T>的runLeaf(Statement statement, Description description, RunNotifier notifier)方法完成的。junit-extension對runLeaf做了一個簡單的封裝,如果運(yùn)行失敗了,測試用例會重新執(zhí)行,重試的次數(shù)通過classRule動態(tài)獲取。
只要測試類是從TestBase派生的就具備了失敗重試的能力了。
@Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
Description description = describeChild(method);
if (method.getAnnotation(Ignore.class) != null) {
notifier.fireTestIgnored(description);
} else {
//runLeaf(methodBlock(method), description, notifier);
Statement statement = methodBlock(method);
int retry = 1;
// 獲取Retry的次數(shù)
for (TestRule rule: classRules()) {
if (Retry.class.isInstance(rule)) {
retry = ((Retry) rule).getCount();
}
}
for (int i = 0; i < retry; i++) {
boolean result = myRunLeaf(statement, description, notifier);
if (result) {
break;
}
}
}
}
private boolean myRunLeaf(Statement statement, Description description, RunNotifier notifier) {
boolean result = false;
EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
eachNotifier.fireTestStarted();
try {
statement.evaluate();
result = true;
} catch (AssumptionViolatedException e) {
eachNotifier.addFailedAssumption(e);
} catch (Throwable e) {
eachNotifier.addFailure(e);
} finally {
eachNotifier.fireTestFinished();
}
return result;
}
報告定制 - CustomRunListener
理解下面5個回調(diào)函數(shù)
- testRunStarted 測試套開始執(zhí)行
- testStarted 單個測試用例開始執(zhí)行
- testFailure 單個測試用例失敗時
- testFinished 單個測試用例結(jié)束時
- testRunFinished 測試套結(jié)束
{
"name": "org.sdet.junit.MyRunner",
"start": 1444790830394,
"end": 1444790830411,
"result": {
"org.sdet.junit.HelloJUnitExtension.shouldFail": [
{
"start": 1444790830400,
"end": 1444790830408,
"pass": false,
"comment": "運(yùn)行失敗的測試用例",
"error": "java.lang.AssertionError: expected:\u003c1\u003e but was:\u003c0\u003e\r\n\tat org.junit.Assert.fail(Assert.java:88)\r\n\tat org.junit.Assert.failNotEquals(Assert.java:743)\r\n\tat org.junit.Assert.assertEquals(Assert.java:118)\r\n\tat org.junit.Assert.assertEquals(Assert.java:555)\r\n\tat org.junit.Assert.assertEquals(Assert.java:542)\r\n\tat org.sdet.junit.HelloJUnitExtension.shouldFail(HelloJUnitExtension.java:20)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)\r\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.lang.reflect.Method.invoke(Method.java:606)\r\n\tat org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)\r\n\tat org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)\r\n\tat org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:49)\r\n\tat org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)\r\n\tat org.junit.runners.BlockJUnit4ClassRunner.myRunLeaf(BlockJUnit4ClassRunner.java:96)\r\n\tat org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:82)\r\n\tat org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:1)\r\n\tat org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)\r\n\tat org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)\r\n\tat org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)\r\n\tat org.junit.runners.ParentRunner.access$0(ParentRunner.java:234)\r\n\tat org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)\r\n\tat org.sdet.junit.extension.rule.Retry$1.evaluate(Retry.java:28)\r\n\tat org.junit.rules.RunRules.evaluate(RunRules.java:20)\r\n\tat org.junit.runners.ParentRunner.run(ParentRunner.java:309)\r\n\tat org.junit.runners.Suite.runChild(Suite.java:127)\r\n\tat org.junit.runners.Suite.runChild(Suite.java:1)\r\n\tat org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)\r\n\tat org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)\r\n\tat org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)\r\n\tat org.junit.runners.ParentRunner.access$0(ParentRunner.java:234)\r\n\tat org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)\r\n\tat org.sdet.junit.extension.CustomSuite.run(CustomSuite.java:39)\r\n\tat org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)\r\n\tat org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)\r\n\tat org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)\r\n\tat org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)\r\n\tat org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)\r\n\tat org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)\r\n"
},
{
"start": 1444790830408,
"end": 1444790830410,
"pass": false,
"comment": "運(yùn)行失敗的測試用例",
"error": "java.lang.AssertionError: expected:\u003c1\u003e but was:\u003c0\u003e\r\n\tat org.junit.Assert.fail(Assert.java:88)\r\n\tat org.junit.Assert.failNotEquals(Assert.java:743)\r\n\tat org.junit.Assert.assertEquals(Assert.java:118)\r\n\tat org.junit.Assert.assertEquals(Assert.java:555)\r\n\tat org.junit.Assert.assertEquals(Assert.java:542)\r\n\tat org.sdet.junit.HelloJUnitExtension.shouldFail(HelloJUnitExtension.java:20)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)\r\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.lang.reflect.Method.invoke(Method.java:606)\r\n\tat org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)\r\n\tat org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)\r\n\tat org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:49)\r\n\tat org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)\r\n\tat org.junit.runners.BlockJUnit4ClassRunner.myRunLeaf(BlockJUnit4ClassRunner.java:96)\r\n\tat org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:82)\r\n\tat org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:1)\r\n\tat org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)\r\n\tat org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)\r\n\tat org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)\r\n\tat org.junit.runners.ParentRunner.access$0(ParentRunner.java:234)\r\n\tat org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)\r\n\tat org.sdet.junit.extension.rule.Retry$1.evaluate(Retry.java:28)\r\n\tat org.junit.rules.RunRules.evaluate(RunRules.java:20)\r\n\tat org.junit.runners.ParentRunner.run(ParentRunner.java:309)\r\n\tat org.junit.runners.Suite.runChild(Suite.java:127)\r\n\tat org.junit.runners.Suite.runChild(Suite.java:1)\r\n\tat org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)\r\n\tat org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)\r\n\tat org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)\r\n\tat org.junit.runners.ParentRunner.access$0(ParentRunner.java:234)\r\n\tat org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)\r\n\tat org.sdet.junit.extension.CustomSuite.run(CustomSuite.java:39)\r\n\tat org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)\r\n\tat org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)\r\n\tat org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)\r\n\tat org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)\r\n\tat org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)\r\n\tat org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)\r\n"
}
],
"org.sdet.junit.HelloJUnitExtension.shouldPass": [
{
"start": 1444790830411,
"end": 1444790830411,
"pass": true,
"comment": "運(yùn)行成功的測試用例",
"error": ""
}
]
}
}
運(yùn)行界面
2015-10-23 23:30:30: TestSuite【org.sdet.junit.MyRunner】 - 開始
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2015-10-23 23:30:30: TestCase【org.sdet.junit.HelloJUnitExtension.shouldFail】 - 第1次測試 - 開始
2015-10-23 23:30:30: TestCase【org.sdet.junit.HelloJUnitExtension.shouldFail】 - 第1次測試 - 結(jié)束 - 【結(jié)果:失敗】
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2015-10-23 23:30:30: TestCase【org.sdet.junit.HelloJUnitExtension.shouldFail】 - 第2次測試 - 開始
2015-10-23 23:30:30: TestCase【org.sdet.junit.HelloJUnitExtension.shouldFail】 - 第2次測試 - 結(jié)束 - 【結(jié)果:失敗】
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2015-10-23 23:30:30: TestCase【org.sdet.junit.HelloJUnitExtension.shouldPass】 - 第1次測試 - 開始
2015-10-23 23:30:30: TestCase【org.sdet.junit.HelloJUnitExtension.shouldPass】 - 第1次測試 - 結(jié)束 - 【結(jié)果:通過】
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2015-10-23 23:30:30: TestSuite【org.sdet.junit.MyRunner】 - 結(jié)束