Groovy解析Junit report 填充email信息

2020-04-12  本文已影响0人  jaymz明

我们一直想做一件事情,就是在email中能够加入一些automation report的信息。曾经我们做过几个尝试:

  1. 将automation report用pod起在那边,作为一个后端service,和当前的环境处于同一个生命周期。
  2. 开发一个长活的service,无论哪个平台的automation 数据,都往这个平台发送数据并存储。用户想troubleshooting的时候,只需要访问这个平台就行。
    针对第一种情况,我们还做了很酷炫的report template,饼图啥的都有。但是发现迁移性很差,而且兼容性不是很给力。只适用于一类环境的report结果。而我们知道部署环境对于跑case的要求也是多种多样。举个例子(nightly build的,可能需要所有case,PP build的,需要gatekeeper的case等等。)
    第二种情况设计和兼容性都没问题,但是存在运维cost,要维护数据储存,平台的可用性。往往都会迎来老板们的challenge。
    那么我们只能综合经济/技术等方面的因素,自己简单开发一个解析junit的脚本了。

先看效果图:

image.png image.png
import java.text.DecimalFormat
class PackageInfo{
    double duration
    int fail
    int skip
    int pass
    int total
    String packageName
    String getPackageName() {
        return packageName
    }
    void setPackageName(String packageName) {
        this.packageName = packageName
    }
    double getDuration() {
        return duration
    }
    void setDuration(double duration) {
        this.duration = duration
    }
    int getFail() {
        return fail
    }
    void setFail(int fail) {
        this.fail = fail
    }
    int getSkip() {
        return skip
    }
    void setSkip(int skip) {
        this.skip = skip
    }
    int getPass() {
        return pass
    }
    void setPass(int pass) {
        this.pass = pass
    }
    int getTotal() {
        return total
    }
    void setTotal(int total) {
        this.total = total
    }
}
class TestCaseInfo{
    int age
    double duration
    String className
    String status
    String name
    String errorDetail
    String errorStackTrace
    boolean skipped

    String getErrorStackTrace() {
        return errorStackTrace
    }

    void setErrorStackTrace(String errorStackTrace) {
        this.errorStackTrace = errorStackTrace
    }

    boolean getSkipped() {
        return skipped
    }

    void setSkipped(boolean skipped) {
        this.skipped = skipped
    }

    String getErrorDetail() {
        return errorDetail
    }

    void setErrorDetail(String errorDetail) {
        this.errorDetail = errorDetail
    }

    int getAge() {
        return age
    }

    void setAge(int age) {
        this.age = age
    }

    double getDuration() {
        return duration
    }

    void setDuration(double duration) {
        this.duration = duration
    }

    String getClassName() {
        return className
    }

    void setClassName(String className) {
        this.className = className
    }

    String getStatus() {
        return status
    }

    void setStatus(String status) {
        this.status = status
    }

    String getName() {
        return name
    }

    void setName(String name) {
        this.name = name
    }
}

class TestResult{
    int failCount
    int passCount
    int skipCount
    List<TestCaseInfo> allCaseInfos = new ArrayList<>()
    List<TestCaseInfo> caseFailInfos = new ArrayList<>()
    TestResult(failCount, passCount, skipCount, allCaseInfos, caseFailInfos){
        this.failCount = failCount
        this.passCount = passCount
        this.skipCount = skipCount
        this.allCaseInfos = allCaseInfos
        this.caseFailInfos = caseFailInfos

    }

    int getFailCount() {
        return failCount
    }

    void setFailCount(int failCount) {
        this.failCount = failCount
    }

    int getPassCount() {
        return passCount
    }

    void setPassCount(int passCount) {
        this.passCount = passCount
    }

    int getSkipCount() {
        return skipCount
    }

    void setSkipCount(int skipCount) {
        this.skipCount = skipCount
    }

    List<TestCaseInfo> getAllCaseInfos() {
        return allCaseInfos
    }

    void setAllCaseInfos(List<TestCaseInfo> casePassInfos) {
        this.allCaseInfos = casePassInfos
    }

    List<TestCaseInfo> getCaseFailInfos() {
        return caseFailInfos
    }

    void setCaseFailInfos(List<TestCaseInfo> caseFailInfos) {
        this.caseFailInfos = caseFailInfos
    }
}

def getJunitResult(String logUrl){
    def result = httpRequest url:logUrl+'testReport/api/json?pretty=true', authentication: 'github-ci'
    def states = readJSON text:result.content
    String[] suites = states.suites
    List<TestCaseInfo> allCaseInfos = new ArrayList<>()
    List<TestCaseInfo> caseFailInfos = new ArrayList<>()
     if(suites != null) {
        for (int i = 0; i < suites.size(); i++) {
            String[] caseSize = states.suites[i].cases

            for(int m=0;m<caseSize.size();m++) {
                def tmp = states.suites[i].cases[m]
                String errorDetail = tmp.errorDetails
                String errorStackTrace = tmp.errorStackTrace

                TestCaseInfo testCaseInfo = new TestCaseInfo()
                testCaseInfo.setAge(tmp.age)
                testCaseInfo.setDuration(tmp.duration)
                testCaseInfo.setClassName(tmp.className+tmp.name)
                testCaseInfo.setStatus(tmp.status)
                testCaseInfo.setName(tmp.name)
                if(errorDetail != null) {
                    testCaseInfo.setErrorDetail(errorDetail)
                }
                testCaseInfo.setSkipped(tmp.skipped)
                if(errorStackTrace != null) {
                    testCaseInfo.setErrorStackTrace(errorStackTrace)
                }
                if (errorDetail != null || errorStackTrace != null) {
                    if(!testCaseInfo.getSkipped()) {
                        caseFailInfos.add(testCaseInfo)
                    }
                }
                allCaseInfos.add(testCaseInfo)

            }

        }
    }
    TestResult testResult = new TestResult(states.failCount,
            states.passCount,
            states.skipCount,allCaseInfos,caseFailInfos)
    return testResult
}

def handlePackageName(Map<String,PackageInfo> map,PackageInfo packageInfo){
    String packageName = packageInfo.getPackageName()
    String key = packageName.substring(0,packageName.lastIndexOf('.'))
    if(map == null || !map.containsKey(key)){
        map.put(key,packageInfo)
    }else{
        PackageInfo tmp = map.get(key)
        DecimalFormat df = new DecimalFormat("#.0")
        double dura = df.format(tmp.getDuration()+packageInfo.getDuration())
        tmp.setDuration(dura)
        tmp.setFail(tmp.getFail()+packageInfo.getFail())
        tmp.setPass(tmp.getPass()+packageInfo.getPass())
        tmp.setSkip(tmp.getSkip()+packageInfo.getSkip())
        tmp.setTotal(tmp.getTotal()+packageInfo.getTotal())
        map.put(key,tmp)

    }
    return map
}

def drawJunitHtml(template,testInfo,archive_reports,test_result){
    if(testInfo == null){
        return ''
    }
    TestResult testResult = (TestResult)testInfo
    String failCaseHtml = ''
    List<TestCaseInfo> caseFailInfos = testResult.getCaseFailInfos()

    if(caseFailInfos != null){
        for(int i=0;i<caseFailInfos.size();i++){
            String detailFailHtml = """
              <tr>
                <td class="pane no-wrap">&nbsp;<font color="red">${caseFailInfos.get(i).getClassName()}</font></td>
                    <td data="0.01" style="text-align:right;" class="pane no-wrap">${caseFailInfos.get(i).getDuration()*1000} ms</td>
                <td class="pane" style="text-align:right;">${caseFailInfos.get(i).getAge()}</td>
            </tr>

            """
            failCaseHtml+=detailFailHtml
        }
    }

    if(failCaseHtml == ""){
        failCaseHtml = """
             <tr>
                <td class="pane no-wrap">&nbsp;NULL</td>
                    <td data="0.01" style="text-align:right;" class="pane no-wrap">NULL</td>
                <td class="pane" style="text-align:right;">NULL</td>
            </tr>

        """
    }


    List<TestCaseInfo> allCaseInfos = testResult.getAllCaseInfos()
    Map<String,PackageInfo> map = new HashMap<>()
    if(allCaseInfos != null) {
        for (int j = 0; j < allCaseInfos.size(); j++) {
            PackageInfo packageInfo = new PackageInfo()
            TestCaseInfo testCaseInfo = allCaseInfos.get(j)
            if (testCaseInfo.getStatus() == 'PASSED' || testCaseInfo.getStatus() == 'FIXED') {
                packageInfo.setPass(1)
            }
            packageInfo.setDuration(testCaseInfo.getDuration())
            packageInfo.setPackageName(testCaseInfo.getClassName())


            if (testCaseInfo.getErrorDetail() != null || testCaseInfo.getErrorStackTrace() != null) {
                if(!testCaseInfo.getSkipped()) {
                    packageInfo.setFail(1)
                }
            }
            if (testCaseInfo.getSkipped()) {
                packageInfo.setSkip(1)
            }
            packageInfo.setTotal(packageInfo.getSkip() + packageInfo.getPass() + packageInfo.getFail())
            map = handlePackageName(map, packageInfo)
        }
    }
    String allCaseHtml = ''
    if(map != null){
        String[] keys = map.keySet().toArray()
        for(int k=0;k<keys.length;k++){
            String caseDetailHtml = """
          <tr>
            <td class="pane">
            <span style="">${keys[k]}</span></a>
            </td>
                <td data="0.020000001" style="text-align:right" class="pane no-wrap">${map.get(keys[k]).getDuration()} s</td>
            <td class="pane" style="text-align:right"><font color="red">${map.get(keys[k]).getFail()}</font></td>
                <td class="pane" style="text-align:right">${map.get(keys[k]).getSkip()}</td>
                <td class="pane" style="text-align:right">${map.get(keys[k]).getPass()}</td>
                <td class="pane" style="text-align:right">${map.get(keys[k]).getTotal()}</td>
            </tr>
        """
            allCaseHtml += caseDetailHtml
        }
    }
    int total = testResult.getFailCount()+testResult.getPassCount()+testResult.getSkipCount()
    html = String.format(template,testResult.getFailCount(),testResult.getPassCount(),testResult.getSkipCount(),total,test_result,test_result,archive_reports,archive_reports,failCaseHtml,allCaseHtml)
    Map resultMap = new HashMap<>()
    resultMap.put("html",html)
    if(testResult.getFailCount()>0){
        resultMap.put("status",'Unstable')
    }else{
        resultMap.put("status","SUCCESS")
    }
    return resultMap
}
return this

外部传入report 模版,并将数据junit的report路径传递给该函数即可。由于返回的也是html,所以我们可以把这部分信息加到任何需要传入case信息的email中去。

testInfos = parseReportUtil.getJunitResult(buildUrl)
Map resultMap = parseReportUtil.drawJunitHtml(templateReport,testInfos,archive_reports,test_result)

report模版也很简单:

<html>
<head resurl="/jenkins/static/ef2a5808" data-rooturl="/jenkins" data-resurl="/jenkins/static/ef2a5808">
    <title>Test Results [Jenkins]</title>
</head>
<body data-model-type="hudson.tasks.junit.TestResult" id="jenkins" class="yui-skin-sam two-column jenkins-2.138.4" data-version="2.138.4">
<div id="main-panel">
    <h4>Test Result</h4>
    <div>
        <h5><font color="red"><span style="margin-left: 5px">failed %s</span></font> <font color="green"><span style="margin-left: 5px">passed %s </span></font> <span style="margin-left: 5px">skipped %s</span>  <span style="margin-left: 5px">total %s </span></h5>
    </div>
    <h4>Test Report</h4>
    <table class="pane sortable bigtable stripped" id="testresult">
        <tbody>
        <tr>
            <td>test report path:</td>
            <td><a href=%s>%s</a></td>
        </tr>
        </tbody>
    </table>
    <h4>Log Link</h4>
    <table class="pane sortable bigtable stripped" id="testresult">
        <tbody>
        <tr>
            <td>test logs path:</td>
            <td><a href=%s>%s</a></td>
        </tr>
        </tbody>

    </table>
</div>

<table style="margin-top: 1em; margin-left:0em;"></table>
<h4>All Failed Tests</h4>
<table class="pane sortable bigtable stripped">
    <tbody>
    <tr>
        <td class="pane-header">Test Name</td>
        <td class="pane-header" style="width:4em"><span class="sortarrow">Duration</span></td>
        <td class="pane-header" style="width:2em"><span class="sortarrow">Age</span></td>
    </tr>
    %s
    </tbody>
</table>
<h4>All Tests</h4>
<table class="pane sortable bigtable stripped" id="testresult">
    <tbody>
    <tr>
        <td class="pane-header">
            package<span class="sortarrow"></span>
        </td>
        <td class="pane-header" style="width:5em; text-align:right">
            duration<span class="sortarrow"></span>
        </td>
        <td class="pane-header" style="width:5em; text-align:right">
            failed<span class="sortarrow"></span>
        </td>

        <td class="pane-header" style="width:5em; text-align:right">
            skipped<span class="sortarrow"></span>
        </td>

        <td class="pane-header" style="width:5em; text-align:right">
            passed<span class="sortarrow"></span>
        </td>

        <td class="pane-header" style="width:5em; text-align:right">
            total<span class="sortarrow"></span>
        </td>
    </tr>
    </tbody>
    <tbody>
    %s
    </tbody>
</table>

</body></html>
 emailext to: to,
                    subject: subject,
                    body: html+caseHtml,
                    mimeType: "text/html"

说点好处就是任何report,只要是junit生成的,都可以用它来解析,而且代码量也不是很多,跨平台,可反复运行。

上一篇下一篇

猜你喜欢

热点阅读