JUnit擴(kuò)展

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é)束
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容