美文网首页
Groovy解析Junit report 填充email信息

Groovy解析Junit report 填充email信息

作者: jaymz明 | 来源:发表于2020-04-12 19:03 被阅读0次

    我们一直想做一件事情,就是在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生成的,都可以用它来解析,而且代码量也不是很多,跨平台,可反复运行。

    相关文章

      网友评论

          本文标题:Groovy解析Junit report 填充email信息

          本文链接:https://www.haomeiwen.com/subject/uxlvmhtx.html