/**
* @author liuzhengwei
* @date 2020/8/5 10:52
* @description
*/
public class ReporterListener implements IReporter {
@Override
public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
Map<String, Object> result = new HashMap<>();
List<ITestResult> list = new LinkedList<>();
Date startDate = new Date();
Date endDate = new Date();
int TOTAL = 0;
int SUCCESS = 0;
int FAILED = 0;
int ERROR = 0;
int SKIPPED = 0;
for (ISuite suite : suites) {
Map<String, ISuiteResult> suiteResults = suite.getResults();
for (ISuiteResult suiteResult : suiteResults.values()) {
ITestContext testContext = suiteResult.getTestContext();
startDate = startDate.getTime()>testContext.getStartDate().getTime()?testContext.getStartDate():startDate;
if (endDate==null) {
endDate = testContext.getEndDate();
} else {
endDate = endDate.getTime()<testContext.getEndDate().getTime()?testContext.getEndDate():endDate;
}
IResultMap passedTests = testContext.getPassedTests();
IResultMap failedTests = testContext.getFailedTests();
IResultMap skippedTests = testContext.getSkippedTests();
IResultMap failedConfig = testContext.getFailedConfigurations();
SUCCESS += passedTests.size();
FAILED += failedTests.size();
SKIPPED += skippedTests.size();
ERROR += failedConfig.size();
list.addAll(this.listTestResult(passedTests));
list.addAll(this.listTestResult(failedTests));
list.addAll(this.listTestResult(skippedTests));
list.addAll(this.listTestResult(failedConfig));
}
}
/* 计算总数 */
TOTAL = SUCCESS + FAILED + SKIPPED + ERROR;
this.sort(list);
Map<String, TestResultCollection> collections = this.parse(list);
VelocityContext context = new VelocityContext();
context.put("TOTAL", TOTAL);
context.put("SUCCESS", SUCCESS);
context.put("FAILED", FAILED);
context.put("ERROR", ERROR);
context.put("SKIPPED", SKIPPED);
context.put("startTime", ReportUtil.formatDate(startDate.getTime()));
context.put("DURATION", ReportUtil.formatDuration(endDate.getTime()-startDate.getTime()));
context.put("results", collections);
write(context, outputDirectory);
//发送邮件
SendHtmlMail.autoSendMail(Arrays.asList("liuzhengwei@sdxnetcafe.com"));
}
private void write(VelocityContext context, String outputDirectory) {
try {
//写文件
VelocityEngine ve = new VelocityEngine();
Properties p = new Properties();
p.setProperty("resource.loader", "class");
p.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
p.setProperty(Velocity.ENCODING_DEFAULT, "utf-8");
p.setProperty(Velocity.INPUT_ENCODING, "utf-8");
p.setProperty(Velocity.OUTPUT_ENCODING, "utf-8");
ve.init(p);
Template t = ve.getTemplate("report.vm");
OutputStream out = new FileOutputStream(new File(outputDirectory+"/report.html"));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, "utf-8"));
// 转换输出
t.merge(context, writer);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
private void sort(List<ITestResult> list){
Collections.sort(list, new Comparator<ITestResult>() {
@Override
public int compare(ITestResult r1, ITestResult r2) {
if(r1.getStartMillis()>r2.getStartMillis()){
return 1;
}else{
return -1;
}
}
});
}
private LinkedList<ITestResult> listTestResult(IResultMap resultMap){
Set<ITestResult> results = resultMap.getAllResults();
return new LinkedList<ITestResult>(results);
}
private Map<String, TestResultCollection> parse(List<ITestResult> list) {
Map<String, TestResultCollection> collectionMap = new HashMap<>();
for (ITestResult t: list) {
String className = t.getTestClass().getName();
if (collectionMap.containsKey(className)) {
TestResultCollection collection = collectionMap.get(className);
collection.addTestResult(toTestResult(t));
} else {
TestResultCollection collection = new TestResultCollection();
collection.addTestResult(toTestResult(t));
collectionMap.put(className, collection);
}
}
return collectionMap;
}
private TestResult toTestResult(ITestResult t) {
TestResult testResult = new TestResult();
Object[] params = t.getParameters();
if (params != null && params.length>=1){
String caseId = (String) params[0];
testResult.setCaseName(caseId);
} else {
testResult.setCaseName("null");
}
testResult.setClassName(t.getTestClass().getName());
testResult.setParams(ReportUtil.getParams(t));
testResult.setTestName(t.getName());
testResult.setStatus(t.getStatus());
testResult.setThrowable(t.getThrowable());
long duration = t.getEndMillis() - t.getStartMillis();
testResult.setDuration(ReportUtil.formatDuration(duration));
testResult.setDescription(t.getMethod().getDescription());
testResult.setOutput(Reporter.getOutput(t));
return testResult;
}
}
/**
* @author liuzhengwei
* @date 2020/8/5 10:54
* @description
*/
public class ReportUtil {
private static final NumberFormat DURATION_FORMAT = new DecimalFormat("#0.000");
private static final NumberFormat PERCENTAGE_FORMAT = new DecimalFormat("#0.00%");
public static String formatDate(long date){
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return formatter.format(date);
}
/**
* 测试消耗时长
* return 秒,保留3位小数
*/
public String getTestDuration(ITestContext context) {
long duration;
duration = context.getEndDate().getTime() - context.getStartDate().getTime();
return formatDuration(duration);
}
public static String formatDuration(long elapsed) {
double seconds = (double) elapsed / 1000;
return DURATION_FORMAT.format(seconds);
}
/**
* 测试通过率
* return 2.22%,保留2位小数
*/
public String formatPercentage(int numerator, int denominator) {
return PERCENTAGE_FORMAT.format(numerator / (double) denominator);
}
/**
* 获取方法参数,以逗号分隔
*
* @param result
* @return
*/
public static String getParams(ITestResult result) {
Object[] params = result.getParameters();
List<String> list = new ArrayList<String>(params.length);
for (Object o : params) {
list.add(renderArgument(o));
}
return commaSeparate(list);
}
/**
* 获取依赖的方法
*
* @param result
* @return
*/
public String getDependMethods(ITestResult result) {
String[] methods = result.getMethod().getMethodsDependedUpon();
return commaSeparate(Arrays.asList(methods));
}
/**
* 堆栈轨迹,暂不确定怎么做,放着先
*
* @param throwable
* @return
*/
public String getCause(Throwable throwable) {
StackTraceElement[] stackTrace = throwable.getStackTrace(); //堆栈轨迹
List<String> list = new ArrayList<String>(stackTrace.length);
for (Object o : stackTrace) {
list.add(renderArgument(o));
}
return commaSeparate(list);
}
/**
* 获取全部日志输出信息
*
* @return
*/
public List<String> getAllOutput() {
return Reporter.getOutput();
}
/**
* 按testresult获取日志输出信息
*
* @param result
* @return
*/
public List<String> getTestOutput(ITestResult result) {
return Reporter.getOutput(result);
}
/*将object 转换为String*/
private static String renderArgument(Object argument) {
if (argument == null) {
return "null";
} else if (argument instanceof String) {
return "\"" + argument + "\"";
} else if (argument instanceof Character) {
return "\'" + argument + "\'";
} else {
return argument.toString();
}
}
/*将集合转换为以逗号分隔的字符串*/
private static String commaSeparate(Collection<String> strings) {
StringBuilder buffer = new StringBuilder();
Iterator<String> iterator = strings.iterator();
while (iterator.hasNext()) {
String string = iterator.next();
buffer.append(string);
if (iterator.hasNext()) {
buffer.append(", ");
}
}
return buffer.toString();
}
}
package com.sdx.test.util.testReporter;
import com.aventstack.extentreports.utils.ExceptionUtil;
import java.util.List;
/**
* @author liuzhengwei
* @date 2020/8/5 10:48
* @description 测试结果类
*/
public class TestResult {
private String testName; //测试方法名
private String className; //测试类名
private String caseName;
private String params; //测试用参数
private String description; //测试描述
private List<String> output; //Reporter Output
private Throwable throwable; //测试异常原因
private String throwableTrace;
private int status; //状态
private String duration;
private boolean success;
public String getTestName() {
return testName;
}
public void setTestName(String testName) {
this.testName = testName;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getCaseName() {
return caseName;
}
public void setCaseName(String caseName) {
this.caseName = caseName;
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<String> getOutput() {
return output;
}
public void setOutput(List<String> output) {
this.output = output;
}
public Throwable getThrowable() {
return throwable;
}
public void setThrowable(Throwable throwable) {
this.throwable = throwable;
this.throwableTrace = ExceptionUtil.getStackTrace(throwable);
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getDuration() {
return duration;
}
public void setDuration(String duration) {
this.duration = duration;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getThrowableTrace() {
return throwableTrace;
}
public void setThrowableTrace(String throwableTrace) {
this.throwableTrace = throwableTrace;
}
}
package com.sdx.test.util.testReporter;
import org.testng.ITestResult;
import java.util.LinkedList;
import java.util.List;
/**
* @author liuzhengwei
* @date 2020/8/5 10:48
* @description 集合类
*/
public class TestResultCollection {
private int totalSize = 0;
private int successSize = 0;
private int failedSize = 0;
private int errorSize = 0;
private int skippedSize = 0;
private List<TestResult> resultList;
public void addTestResult(TestResult result) {
if (resultList == null) {
resultList = new LinkedList<>();
}
resultList.add(result);
switch (result.getStatus()) {
case ITestResult.FAILURE:
failedSize+=1;
break;
case ITestResult.SUCCESS:
successSize+=1;
break;
case ITestResult.SKIP:
skippedSize+=1;
break;
}
totalSize+=1;
}
/*===============================[getter && setter]=================================*/
public int getTotalSize() {
return totalSize;
}
public void setTotalSize(int totalSize) {
this.totalSize = totalSize;
}
public int getSuccessSize() {
return successSize;
}
public void setSuccessSize(int successSize) {
this.successSize = successSize;
}
public int getFailedSize() {
return failedSize;
}
public void setFailedSize(int failedSize) {
this.failedSize = failedSize;
}
public int getErrorSize() {
return errorSize;
}
public void setErrorSize(int errorSize) {
this.errorSize = errorSize;
}
public int getSkippedSize() {
return skippedSize;
}
public void setSkippedSize(int skippedSize) {
this.skippedSize = skippedSize;
}
public List<TestResult> getResultList() {
return resultList;
}
public void setResultList(List<TestResult> resultList) {
this.resultList = resultList;
}
}
/**
* @author liuzhengwei
* @date 2020/8/5 11:25
* @description
*/
public class SendHtmlMail {
private static void sendMessage(List<String> to, String title, String messageText)
throws MessagingException, java.io.UnsupportedEncodingException {
String smtpHost = "smtp.qq.com";
String from = "xxxxxx@qq.com";
// Step 1: Configure the mail session
// System.out.println("Configuring mail session for: " + smtpHost);
java.util.Properties props = new java.util.Properties();
props.setProperty("mail.smtp.auth", "true");//指定是否需要SMTP验证
props.setProperty("mail.smtp.host", smtpHost);//指定SMTP服务器
props.put("mail.transport.protocol", "smtp");
Session mailSession = Session.getDefaultInstance(props);
mailSession.setDebug(false);//是否在控制台显示debug信息
InternetAddress fromAddress = new InternetAddress(from);
InternetAddress[] sendTo = new InternetAddress[to.size()];
for (int i = 0; i < to.size(); i++) {
sendTo[i] = new InternetAddress(to.get(i));
}
MimeMessage testMessage = new MimeMessage(mailSession);
testMessage.setFrom(fromAddress);
testMessage.setRecipients(javax.mail.Message.RecipientType.TO, sendTo);
testMessage.setSentDate(new java.util.Date());
testMessage.setSubject(MimeUtility.encodeText(title, "utf-8", "B"));
testMessage.setContent(messageText, "text/html;charset=utf-8");
// Step 3: Now send the message
Transport transport = mailSession.getTransport("smtp");
transport.connect(smtpHost, from, "授权码");//qq邮箱设置可打开获取
transport.sendMessage(testMessage, testMessage.getAllRecipients());
transport.close();
}
public static void autoSendMail(List<String> toEmailAdress) {
// String to = "xxxxx@qq.com";
String subject = "自动化报告"; //subject javamail自动转码
String property = System.getProperty("user.dir")+"\\test-output\\report.html";
String html = readFileContent(property);
try {
SendHtmlMail.sendMessage(toEmailAdress, subject, html);
} catch (javax.mail.MessagingException exc) {
exc.printStackTrace();
} catch (java.io.UnsupportedEncodingException exc) {
exc.printStackTrace();
}
}
private static String readFileContent(String fileName) {
File file = new File(fileName);
BufferedReader reader = null;
StringBuffer sbf = new StringBuffer();
try {
reader = new BufferedReader(new FileReader(file));
String tempStr;
while ((tempStr = reader.readLine()) != null) {
sbf.append(tempStr);
}
reader.close();
return sbf.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
return sbf.toString();
}
}
编辑report.vm
<head> <meta content="text/html; charset=utf-8" http-equiv="content-type"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title> - TestReport</title> <style> body { background-color: #f2f2f2; color: #333; margin: 0 auto; width: 960px; } #summary { width: 960px; margin-bottom: 20px; } #summary th { background-color: skyblue; padding: 5px 12px; } #summary td { background-color: lightblue; text-align: center; padding: 4px 8px; } .details { width: 960px; margin-bottom: 20px; } .details th { background-color: skyblue; padding: 5px 12px; } .details tr .passed { background-color: lightgreen; } .details tr .failed { background-color: red; } .details tr .unchecked { background-color: gray; } .details td { background-color: lightblue; padding: 5px 12px; } .details .detail { background-color: lightgrey; font-size: smaller; padding: 5px 10px; text-align: center; } .details .success { background-color: greenyellow; } .details .error { background-color: red; } .details .failure { background-color: salmon; } .details .skipped { background-color: gray; } .button { font-size: 1em; padding: 6px; width: 4em; text-align: center; background-color: #06d85f; border-radius: 20px/50px; cursor: pointer; transition: all 0.3s ease-out; } a.button { color: gray; text-decoration: none; } .button:hover { background: #2cffbd; } .overlay { position: fixed; top: 0; bottom: 0; left: 0; right: 0; background: rgba(0, 0, 0, 0.7); transition: opacity 500ms; visibility: hidden; opacity: 0; } .overlay:target { visibility: visible; opacity: 1; } .popup { margin: 70px auto; padding: 20px; background: #fff; border-radius: 10px; width: 50%; position: relative; transition: all 3s ease-in-out; } .popup h2 { margin-top: 0; color: #333; font-family: Tahoma, Arial, sans-serif; } .popup .close { position: absolute; top: 20px; right: 30px; transition: all 200ms; font-size: 30px; font-weight: bold; text-decoration: none; color: #333; } .popup .close:hover { color: #06d85f; } .popup .content { max-height: 80%; overflow: auto; text-align: left; } @media screen and (max-width: 700px) { .box { width: 70%; } .popup { width: 70%; } } </style> </head> <body> <h1>自动化测试报告: </h1> <h2>汇总</h2> <table id="summary"> <tr> <th>START AT</th> <td colspan="4">${startTime}</td> </tr> <tr> <th>DURATION</th> <td colspan="4">$DURATION seconds</td> </tr> <tr> <th>TOTAL</th> <th>SUCCESS</th> <th>FAILED</th> <th>ERROR</th> <th>SKIPPED</th> </tr> <tr> <td>$TOTAL</td> <td>$SUCCESS</td> <td>$FAILED</td> <td>$ERROR</td> <td>$SKIPPED</td> </tr> </table> <h2>详情</h2> #foreach($result in $results.entrySet()) #set($item = $result.value) <table id="$result.key" class="details"> <tr> <th>测试类</th> <td colspan="4">$result.key</td> </tr> <tr> <td>TOTAL: $item.totalSize</td> <td>SUCCESS: $item.successSize</td> <td>FAILED: $item.failedSize</td> <td>ERROR: $item.errorSize</td> <td>SKIPPED: $item.skippedSize</td> </tr> <tr> <th>Method</th> <th>Description</th> <th>Duration</th> <th>Status</th> <th>Detail</th> </tr> #foreach($testResult in $item.resultList) <tr id="${result.key}.${testResult.caseName}.${testResult.testName}"> <td>${testResult.testName}</td> <td>$testResult.description</td> <td>${testResult.duration} seconds</td> #if($testResult.status==1) <td class="success" style="width:5em;">success</td> #elseif($testResult.status==2) <td class="failure" style="width:5em;">failure</td> #elseif($testResult.status==3) <td class="skipped" style="width:5em;">skipped</td> #end <td class="detail"> <a class="button" href="#popup_log_${testResult.caseName}_${testResult.testName}">log</a> <div id="popup_log_${testResult.caseName}_${testResult.testName}" class="overlay"> <div class="popup"> <h2>Request and Response data</h2> <a class="close" href="">×</a> <div class="content"> <h3>Response:</h3> <div style="overflow: auto"> <table> <tr> <th>日志</th> <td> #foreach($msg in $testResult.output) <pre>$msg</pre> #end </td> </tr> #if($testResult.status==2) <tr> <th>异常</th> <td> <pre style="white-space : normal">$testResult.throwableTrace</pre> </td> </tr> #end </table> </div> </div> </div> </div> </td> </tr> #end </table> #end </body>