2013-10-24 39 views
6

TestNG生成電子郵件報告。我已經看到這個報告可以通過使用Listeners來定製。但無法得到我想要的。我的要求是在本報告的摘要部分中包含額外的詳細信息。我希望能夠添加一個新的表格或額外的列來顯示測試執行的環境細節。自定義TestNG電子郵件報告的摘要部分

試圖附加截圖,但顯然缺少的東西,它不出現。

enter image description here

+2

我使用TestNG的6.8.7和運行我使用Maven的webdriver測試的右上角獲得摘要詳細信息。我寫了覆蓋onTestFailure(),並調用Reporter.log(即把一個超鏈接到截圖TestNG的報告(的index.html)的自定義監聽器)。但它不適用於emailable-report.html。定製電子郵件報告是將截圖附加到此報告的唯一方法嗎? – Ziska

+0

該報告僅顯示HTML標記。它不使其作爲電子郵件發送的-report.html的。但是鏈接和屏幕截圖在index.html上正確顯示。 – Ziska

+0

我想你應該嘗試在上面的鏈接中提到的方式嗎?這個對我有用。 – Akbar

回答

6

這就是我對我的框架。我會盡力解釋它(對不起我的英語)

複製ReporterListenerAdapter.java並更名爲MyReporterListenerAdapter.java,把它放在你的Java項目(例如/監聽器文件夾)

public class MyReporterListenerAdapter implements IReporter { 

public void generateReport(List<XmlSuite> xml, List<ISuite> suites, String outdir) {} 
} 

接下來,複製ReporterListener.java並重命名爲MyReporterListener.java

這裏粘貼的代碼太多了,但是createWriter函數更改了報表名稱。例如:「emailable-MyFramework-report」。

MyReporterListener.java

public class MyReporterListener extends MyReporterListenerAdapter { 

    private static final Logger L = Logger.getLogger(MyReporterListener.class); 

    // ~ Instance fields ------------------------------------------------------ 

    private PrintWriter m_out; 

    private int m_row; 

    private Integer m_testIndex; 

    private int m_methodIndex; 

    private Scanner scanner; 

    // ~ Methods -------------------------------------------------------------- 

    /** Creates summary of the run */ 
    @Override 
    public void generateReport(List<XmlSuite> xml, List<ISuite> suites, 
      String outdir) { 
     try { 
      m_out = createWriter(outdir); 
     } catch (IOException e) { 
      L.error("output file", e); 
      return; 
     } 

     startHtml(m_out); 
     generateSuiteSummaryReport(suites); 
     generateMethodSummaryReport(suites); 
     generateMethodDetailReport(suites); 
     endHtml(m_out); 
     m_out.flush(); 
     m_out.close(); 
    } 

    protected PrintWriter createWriter(String outdir) throws IOException { 
     java.util.Date now = new Date(); 
     new File(outdir).mkdirs(); 
     return new PrintWriter(new BufferedWriter(new FileWriter(new File(
       outdir, "emailable-FON-report" 
         + DateFunctions.dateToDayAndTimeForFileName(now) 
         + ".html")))); 
    } 

    /** 
    * Creates a table showing the highlights of each test method with links to 
    * the method details 
    */ 
    protected void generateMethodSummaryReport(List<ISuite> suites) { 
     m_methodIndex = 0; 
     startResultSummaryTable("methodOverview"); 
     int testIndex = 1; 
     for (ISuite suite : suites) { 
      if (suites.size() > 1) { 
       titleRow(suite.getName(), 5); 
      } 
      Map<String, ISuiteResult> r = suite.getResults(); 
      for (ISuiteResult r2 : r.values()) { 
       ITestContext testContext = r2.getTestContext(); 
       String testName = testContext.getName(); 
       m_testIndex = testIndex; 
       resultSummary(suite, testContext.getFailedConfigurations(), 
         testName, "failed", " (configuration methods)"); 
       resultSummary(suite, testContext.getFailedTests(), testName, 
         "failed", ""); 
       resultSummary(suite, testContext.getSkippedConfigurations(), 
         testName, "skipped", " (configuration methods)"); 
       resultSummary(suite, testContext.getSkippedTests(), testName, 
         "skipped", ""); 
       resultSummary(suite, testContext.getPassedTests(), testName, 
         "passed", ""); 
       testIndex++; 
      } 
     } 
     m_out.println("</table>"); 
    } 

    /** Creates a section showing known results for each method */ 
    protected void generateMethodDetailReport(List<ISuite> suites) { 
     m_methodIndex = 0; 
     for (ISuite suite : suites) { 
      Map<String, ISuiteResult> r = suite.getResults(); 
      for (ISuiteResult r2 : r.values()) { 
       ITestContext testContext = r2.getTestContext(); 
       if (r.values().size() > 0) { 
        m_out.println("<h1>" + testContext.getName() + "</h1>"); 
       } 
       resultDetail(testContext.getFailedConfigurations()); 
       resultDetail(testContext.getFailedTests()); 
       resultDetail(testContext.getSkippedConfigurations()); 
       resultDetail(testContext.getSkippedTests()); 
       resultDetail(testContext.getPassedTests()); 
      } 
     } 
    } 

    /** 
    * @param tests 
    */ 
    private void resultSummary(ISuite suite, IResultMap tests, String testname, 
      String style, String details) { 
     if (tests.getAllResults().size() > 0) { 
      StringBuffer buff = new StringBuffer(); 
      String lastClassName = ""; 
      int mq = 0; 
      int cq = 0; 
      for (ITestNGMethod method : getMethodSet(tests, suite)) { 
       m_row += 1; 
       m_methodIndex += 1; 
       ITestClass testClass = method.getTestClass(); 
       String className = testClass.getName(); 
       if (mq == 0) { 
        String id = (m_testIndex == null ? null : "t" 
          + Integer.toString(m_testIndex)); 
        titleRow(testname + " &#8212; " + style + details, 5, id); 
        m_testIndex = null; 
       } 
       if (!className.equalsIgnoreCase(lastClassName)) { 
        if (mq > 0) { 
         cq += 1; 
         m_out.print("<tr class=\"" + style 
           + (cq % 2 == 0 ? "even" : "odd") + "\">" 
           + "<td"); 
         if (mq > 1) { 
          m_out.print(" rowspan=\"" + mq + "\""); 
         } 
         m_out.println(">" + lastClassName + "</td>" + buff); 
        } 
        mq = 0; 
        buff.setLength(0); 
        lastClassName = className; 
       } 
       Set<ITestResult> resultSet = tests.getResults(method); 
       long end = Long.MIN_VALUE; 
       long start = Long.MAX_VALUE; 
       for (ITestResult testResult : tests.getResults(method)) { 
        if (testResult.getEndMillis() > end) { 
         end = testResult.getEndMillis(); 
        } 
        if (testResult.getStartMillis() < start) { 
         start = testResult.getStartMillis(); 
        } 
       } 
       mq += 1; 
       if (mq > 1) { 
        buff.append("<tr class=\"" + style 
          + (cq % 2 == 0 ? "odd" : "even") + "\">"); 
       } 
       String description = method.getDescription(); 
       String testInstanceName = resultSet 
         .toArray(new ITestResult[] {})[0].getTestName(); 
       buff.append("<td><a href=\"#m" 
         + m_methodIndex 
         + "\">" 
         + qualifiedName(method) 
         + " " 
         + (description != null && description.length() > 0 ? "(\"" 
           + description + "\")" 
           : "") 
         + "</a>" 
         + (null == testInstanceName ? "" : "<br>(" 
           + testInstanceName + ")") + "</td>" 
         + "<td class=\"numi\">" + resultSet.size() + "</td>" 
         + "<td>" + start + "</td>" + "<td class=\"numi\">" 
         + (end - start) + "</td>" + "</tr>"); 
      } 
      if (mq > 0) { 
       cq += 1; 
       m_out.print("<tr class=\"" + style 
         + (cq % 2 == 0 ? "even" : "odd") + "\">" + "<td"); 
       if (mq > 1) { 
        m_out.print(" rowspan=\"" + mq + "\""); 
       } 
       m_out.println(">" + lastClassName + "</td>" + buff); 
      } 
     } 
    } 

    /** Starts and defines columns result summary table */ 
    private void startResultSummaryTable(String style) { 
     tableStart(style, "summary"); 
     m_out.println("<tr><th>Class</th>" 
       + "<th>Method</th><th># of<br/>Scenarios</th><th>Start</th><th>Time<br/>(ms)</th></tr>"); 
     m_row = 0; 
    } 

    private String qualifiedName(ITestNGMethod method) { 
     StringBuilder addon = new StringBuilder(); 
     String[] groups = method.getGroups(); 
     int length = groups.length; 
     if (length > 0 && !"basic".equalsIgnoreCase(groups[0])) { 
      addon.append("("); 
      for (int i = 0; i < length; i++) { 
       if (i > 0) { 
        addon.append(", "); 
       } 
       addon.append(groups[i]); 
      } 
      addon.append(")"); 
     } 

     return "<b>" + method.getMethodName() + "</b> " + addon; 
    } 

    private void resultDetail(IResultMap tests) { 
     for (ITestResult result : tests.getAllResults()) { 
      ITestNGMethod method = result.getMethod(); 
      m_methodIndex++; 
      String cname = method.getTestClass().getName(); 
      m_out.println("<h2 id=\"m" + m_methodIndex + "\">" + cname + ":" 
        + method.getMethodName() + "</h2>"); 
      Set<ITestResult> resultSet = tests.getResults(method); 
      generateForResult(result, method, resultSet.size()); 
      m_out.println("<p class=\"totop\"><a href=\"#summary\">back to summary</a></p>"); 

     } 
    } 

    /** 
    * Write the first line of the stack trace 
    * 
    * @param tests 
    */ 
    private void getShortException(IResultMap tests) { 

     for (ITestResult result : tests.getAllResults()) { 
      m_methodIndex++; 
      Throwable exception = result.getThrowable(); 
      List<String> msgs = Reporter.getOutput(result); 
      boolean hasReporterOutput = msgs.size() > 0; 
      boolean hasThrowable = exception != null; 
      if (hasThrowable) { 
       boolean wantsMinimalOutput = result.getStatus() == ITestResult.SUCCESS; 
       if (hasReporterOutput) { 
        m_out.print("<h3>" 
          + (wantsMinimalOutput ? "Expected Exception" 
            : "Failure") + "</h3>"); 
       } 

       // Getting first line of the stack trace 
       String str = Utils.stackTrace(exception, true)[0]; 
       scanner = new Scanner(str); 
       String firstLine = scanner.nextLine(); 
       m_out.println(firstLine); 
      } 
     } 
    } 

    /** 
    * Write all parameters 
    * 
    * @param tests 
    */ 
    private void getParameters(IResultMap tests) { 

     for (ITestResult result : tests.getAllResults()) { 
      m_methodIndex++; 
      Object[] parameters = result.getParameters(); 
      boolean hasParameters = parameters != null && parameters.length > 0; 
      if (hasParameters) { 

       for (Object p : parameters) { 
        m_out.println(Utils.escapeHtml(Utils.toString(p)) + " | "); 
       } 
      } 

     } 
    } 

    private void generateForResult(ITestResult ans, ITestNGMethod method, 
      int resultSetSize) { 
     Object[] parameters = ans.getParameters(); 
     boolean hasParameters = parameters != null && parameters.length > 0; 
     if (hasParameters) { 
      tableStart("result", null); 
      m_out.print("<tr class=\"param\">"); 
      for (int x = 1; x <= parameters.length; x++) { 
       m_out.print("<th>Param." + x + "</th>"); 
      } 
      m_out.println("</tr>"); 
      m_out.print("<tr class=\"param stripe\">"); 
      for (Object p : parameters) { 
       m_out.println("<td>" + Utils.escapeHtml(Utils.toString(p)) 
         + "</td>"); 
      } 
      m_out.println("</tr>"); 
     } 
     List<String> msgs = Reporter.getOutput(ans); 
     boolean hasReporterOutput = msgs.size() > 0; 
     Throwable exception = ans.getThrowable(); 
     boolean hasThrowable = exception != null; 
     if (hasReporterOutput || hasThrowable) { 
      if (hasParameters) { 
       m_out.print("<tr><td"); 
       if (parameters.length > 1) { 
        m_out.print(" colspan=\"" + parameters.length + "\""); 
       } 
       m_out.println(">"); 
      } else { 
       m_out.println("<div>"); 
      } 
      if (hasReporterOutput) { 
       if (hasThrowable) { 
        m_out.println("<h3>Test Messages</h3>"); 
       } 
       for (String line : msgs) { 
        m_out.println(line + "<br/>"); 
       } 
      } 
      if (hasThrowable) { 
       boolean wantsMinimalOutput = ans.getStatus() == ITestResult.SUCCESS; 
       if (hasReporterOutput) { 
        m_out.println("<h3>" 
          + (wantsMinimalOutput ? "Expected Exception" 
            : "Failure") + "</h3>"); 
       } 
       generateExceptionReport(exception, method); 
      } 
      if (hasParameters) { 
       m_out.println("</td></tr>"); 
      } else { 
       m_out.println("</div>"); 
      } 
     } 
     if (hasParameters) { 
      m_out.println("</table>"); 
     } 
    } 

    protected void generateExceptionReport(Throwable exception, 
      ITestNGMethod method) { 
     m_out.print("<div class=\"stacktrace\">"); 
     m_out.print(Utils.stackTrace(exception, true)[0]); 
     m_out.println("</div>"); 
    } 

    /** 
    * Since the methods will be sorted chronologically, we want to return the 
    * ITestNGMethod from the invoked methods. 
    */ 
    private Collection<ITestNGMethod> getMethodSet(IResultMap tests, 
      ISuite suite) { 
     List<IInvokedMethod> r = Lists.newArrayList(); 
     List<IInvokedMethod> invokedMethods = suite.getAllInvokedMethods(); 
     for (IInvokedMethod im : invokedMethods) { 
      if (tests.getAllMethods().contains(im.getTestMethod())) { 
       r.add(im); 
      } 
     } 
     Arrays.sort(r.toArray(new IInvokedMethod[r.size()]), new TestSorter()); 
     List<ITestNGMethod> result = Lists.newArrayList(); 

     // Add all the invoked methods 
     for (IInvokedMethod m : r) { 
      result.add(m.getTestMethod()); 
     } 

     // Add all the methods that weren't invoked (e.g. skipped) that we 
     // haven't added yet 
     for (ITestNGMethod m : tests.getAllMethods()) { 
      if (!result.contains(m)) { 
       result.add(m); 
      } 
     } 
     return result; 
    } 

    @SuppressWarnings("unused") 
    public void generateSuiteSummaryReport(List<ISuite> suites) { 
     tableStart("testOverview", null); 
     m_out.print("<tr>"); 
     tableColumnStart("Test"); 
     tableColumnStart("Methods<br/>Passed"); 
     tableColumnStart("Scenarios<br/>Passed"); 
     tableColumnStart("# skipped"); 
     tableColumnStart("# failed"); 
     tableColumnStart("Error messages"); 
     tableColumnStart("Parameters"); 
     tableColumnStart("Start<br/>Time"); 
     tableColumnStart("End<br/>Time"); 
     tableColumnStart("Total<br/>Time"); 
     tableColumnStart("Included<br/>Groups"); 
     tableColumnStart("Excluded<br/>Groups"); 

     m_out.println("</tr>"); 
     NumberFormat formatter = new DecimalFormat("#,##0.0"); 
     int qty_tests = 0; 
     int qty_pass_m = 0; 
     int qty_pass_s = 0; 
     int qty_skip = 0; 
     int qty_fail = 0; 
     long time_start = Long.MAX_VALUE; 
     long time_end = Long.MIN_VALUE; 
     m_testIndex = 1; 
     for (ISuite suite : suites) { 
      if (suites.size() > 1) { 
       titleRow(suite.getName(), 8); 
      } 
      Map<String, ISuiteResult> tests = suite.getResults(); 
      for (ISuiteResult r : tests.values()) { 
       qty_tests += 1; 
       ITestContext overview = r.getTestContext(); 
       startSummaryRow(overview.getName()); 
       int q = getMethodSet(overview.getPassedTests(), suite).size(); 
       qty_pass_m += q; 
       summaryCell(q, Integer.MAX_VALUE); 
       q = overview.getPassedTests().size(); 
       qty_pass_s += q; 
       summaryCell(q, Integer.MAX_VALUE); 
       q = getMethodSet(overview.getSkippedTests(), suite).size(); 
       qty_skip += q; 
       summaryCell(q, 0); 
       q = getMethodSet(overview.getFailedTests(), suite).size(); 
       qty_fail += q; 
       summaryCell(q, 0); 

       // NEW 
       // Insert error found 
       m_out.print("<td class=\"numi" + (true ? "" : "_attn") + "\">"); 
       getShortException(overview.getFailedTests()); 
       getShortException(overview.getSkippedTests()); 
       m_out.println("</td>"); 

       // NEW 
       // Add parameters for each test case (failed or passed) 
       m_out.print("<td class=\"numi" + (true ? "" : "_attn") + "\">"); 

       // Write OS and Browser 
       // m_out.println(suite.getParameter("os").substring(0, 3) + 
       // " | " 
       // + suite.getParameter("browser").substring(0, 3) + " | "); 

       getParameters(overview.getFailedTests()); 
       getParameters(overview.getPassedTests()); 
       getParameters(overview.getSkippedTests()); 
       m_out.println("</td>"); 

       // NEW 
       summaryCell(
         DateFunctions.dateToDayAndTime(overview.getStartDate()), 
         true); 
       m_out.println("</td>"); 
       summaryCell(
         DateFunctions.dateToDayAndTime(overview.getEndDate()), 
         true); 
       m_out.println("</td>"); 

       time_start = Math.min(overview.getStartDate().getTime(), 
         time_start); 
       time_end = Math.max(overview.getEndDate().getTime(), time_end); 
       summaryCell(
         formatter.format((overview.getEndDate().getTime() - overview 
           .getStartDate().getTime())/1000.) 
           + " seconds", true); 
       summaryCell(overview.getIncludedGroups()); 
       summaryCell(overview.getExcludedGroups()); 
       m_out.println("</tr>"); 
       m_testIndex++; 
      } 
     } 
     if (qty_tests > 1) { 
      m_out.println("<tr class=\"total\"><td>Total</td>"); 
      summaryCell(qty_pass_m, Integer.MAX_VALUE); 
      summaryCell(qty_pass_s, Integer.MAX_VALUE); 
      summaryCell(qty_skip, 0); 
      summaryCell(qty_fail, 0); 
      summaryCell(" ", true); 
      summaryCell(" ", true); 
      summaryCell(" ", true); 
      summaryCell(" ", true); 
      summaryCell(
        formatter.format(((time_end - time_start)/1000.)/60.) 
          + " minutes", true); 
      m_out.println("<td colspan=\"3\">&nbsp;</td></tr>"); 
     } 
     m_out.println("</table>"); 
    } 

    private void summaryCell(String[] val) { 
     StringBuffer b = new StringBuffer(); 
     for (String v : val) { 
      b.append(v + " "); 
     } 
     summaryCell(b.toString(), true); 
    } 

    private void summaryCell(String v, boolean isgood) { 
     m_out.print("<td class=\"numi" + (isgood ? "" : "_attn") + "\">" + v 
       + "</td>"); 
    } 

    private void startSummaryRow(String label) { 
     m_row += 1; 
     m_out.print("<tr" 
       + (m_row % 2 == 0 ? " class=\"stripe\"" : "") 
       + "><td style=\"text-align:left;padding-right:2em\"><a href=\"#t" 
       + m_testIndex + "\">" + label + "</a>" + "</td>"); 
    } 

    private void summaryCell(int v, int maxexpected) { 
     summaryCell(String.valueOf(v), v <= maxexpected); 
    } 

    private void tableStart(String cssclass, String id) { 
     m_out.println("<table cellspacing=\"0\" cellpadding=\"0\"" 
       + (cssclass != null ? " class=\"" + cssclass + "\"" 
         : " style=\"padding-bottom:2em\"") 
       + (id != null ? " id=\"" + id + "\"" : "") + ">"); 
     m_row = 0; 
    } 

    private void tableColumnStart(String label) { 
     m_out.print("<th>" + label + "</th>"); 
    } 

    private void titleRow(String label, int cq) { 
     titleRow(label, cq, null); 
    } 

    private void titleRow(String label, int cq, String id) { 
     m_out.print("<tr"); 
     if (id != null) { 
      m_out.print(" id=\"" + id + "\""); 
     } 
     m_out.println("><th colspan=\"" + cq + "\">" + label + "</th></tr>"); 
     m_row = 0; 
    } 

    /** Starts HTML stream */ 
    protected void startHtml(PrintWriter out) { 
     out.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">"); 
     out.println("<html xmlns=\"http://www.w3.org/1999/xhtml\">"); 
     out.println("<head>"); 
     out.println("<title>Hector Flores - TestNG Report</title>"); 
     out.println("<style type=\"text/css\">"); 
     out.println("table {margin-bottom:10px;border-collapse:collapse;empty-cells:show}"); 
     out.println("td,th {border:1px solid #009;padding:.25em .5em}"); 
     out.println(".result th {vertical-align:bottom}"); 
     out.println(".param th {padding-left:1em;padding-right:1em}"); 
     out.println(".param td {padding-left:.5em;padding-right:2em}"); 
     out.println(".stripe td,.stripe th {background-color: #E6EBF9}"); 
     out.println(".numi,.numi_attn {text-align:right}"); 
     out.println(".total td {font-weight:bold}"); 
     out.println(".passedodd td {background-color: #0A0}"); 
     out.println(".passedeven td {background-color: #3F3}"); 
     out.println(".skippedodd td {background-color: #CCC}"); 
     out.println(".skippedodd td {background-color: #DDD}"); 
     out.println(".failedodd td,.numi_attn {background-color: #F33}"); 
     out.println(".failedeven td,.stripe .numi_attn {background-color: #D00}"); 
     out.println(".stacktrace {white-space:pre;font-family:monospace}"); 
     out.println(".totop {font-size:85%;text-align:center;border-bottom:2px solid #000}"); 
     out.println("</style>"); 
     out.println("</head>"); 
     out.println("<body>"); 
    } 

    /** Finishes HTML stream */ 
    protected void endHtml(PrintWriter out) { 
     out.println("<center> Report customized by Hector Flores [[email protected]] </center>"); 
     out.println("</body></html>"); 
    } 

    // ~ Inner Classes -------------------------------------------------------- 
    /** Arranges methods by classname and method name */ 
    private class TestSorter implements Comparator<IInvokedMethod> { 
     // ~ Methods 
     // ------------------------------------------------------------- 

     /** Arranges methods by classname and method name */ 
     @Override 
     public int compare(IInvokedMethod o1, IInvokedMethod o2) { 
      // System.out.println("Comparing " + o1.getMethodName() + " " + 
      // o1.getDate() 
      // + " and " + o2.getMethodName() + " " + o2.getDate()); 
      return (int) (o1.getDate() - o2.getDate()); 
      // int r = ((T) o1).getTestClass().getName().compareTo(((T) 
      // o2).getTestClass().getName()); 
      // if (r == 0) { 
      // r = ((T) o1).getMethodName().compareTo(((T) o2).getMethodName()); 
      // } 
      // return r; 
     } 
    } 
} 

有了這些步驟,你已經有你的聽衆願意聽。

如何調用它?

如果您使用的testng.xml加上下面幾行:如果你從一個Java類運行測試

<listeners> 
     <listener class-name='[your_class_path].MyReporterListener'/> 
</listeners> 

,加上下面幾行:

private final static MyReporterListener frl = new MyReporterListener(); 
TestNG testng = new TestNG(); 
testng.addListener(frl); 

有了這些措施,當你執行你的測試,你將有兩個電子郵件報告,定製和原創。

現在該是皮條客的時候了。

在我的情況下,我不得不添加錯誤消息,參數和時間(開始和結束),因爲如果您想要粘貼在Excel文件中,它非常有用。

我的自定義報表:

My report

你必須修改主要generateSuiteSummaryReport(名單套房)功能。

玩這個,問我是否有任何問題。

+0

「複製ReporterListenerAdapter.java,並重新命名爲MyReporterListenerAdapter.java」,「複製ReporterListener.java並更名爲MyReporterListener.java」 - 我在哪裏可以找到這些文件? – Akbar

+0

如果您發佈任何代碼或插入的任何圖像,它沒有拿出對後.... – Akbar

+0

那麼你可以找到TestNG的-XXXjar這些類,從Eclipse IDE中調用TestNG的類,然後從你的.jar文件附加源。 但好的,複製我的代碼。看下一個答案。 –

0

你可以更好的使用程度的報告HTML報告庫jar文件中這個鏈接,完整的代碼。不過,我正在使用extentreport1.4.jar jar文件。所以,你會在你的報告