github: junit-extension
背景
原生的Junit無法滿足我們在自動(dòng)化測試實(shí)踐過程中的碰到一些需求族壳,比如
- 管理人員希望統(tǒng)計(jì)測試組每個(gè)成員開發(fā)的用例數(shù)目, 要是每個(gè)用例都能夠有注釋
- 在本地調(diào)試測試用例時(shí)灌诅,測試工程師希望只運(yùn)行于自己開發(fā)的用例
- 系統(tǒng)測試時(shí)需要執(zhí)行全量用例,回歸測試時(shí)希望跑High優(yōu)先級的用例
- 測試用例運(yùn)行失敗后能夠有自動(dòng)重試的機(jī)制
- junit產(chǎn)生的測試報(bào)告希望與內(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)
不會(huì)執(zhí)行俯萌。
// 多出的一行代碼
notifier.fireTestRunStarted(description);
測試用例過濾邏輯 CustomFilter
詳情請參考下面3個(gè)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做了一個(gè)簡單的封裝,如果運(yùn)行失敗了咐熙,測試用例會(huì)重新執(zhí)行弱恒,重試的次數(shù)通過classRule動(dòng)態(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;
}
報(bào)告定制 - CustomRunListener
理解下面5個(gè)回調(diào)函數(shù)
- testRunStarted 測試套開始執(zhí)行
- testStarted 單個(gè)測試用例開始執(zhí)行
- testFailure 單個(gè)測試用例失敗時(shí)
- testFinished 單個(gè)測試用例結(jié)束時(shí)
- 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é)束