报表信息
失败截图
鼠标放上图片放大
popj代码
ReportTotal
/**
* 报表信息
* @author liwen406
* @date 2019-09-25 11:02
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class ReportTotal {
private Integer id;
/**
* 开始时间
*/
private String testUser;
/**
* 结束时间
*/
private String startTime;
/**
* 结束时间
*/
private String endTime;
/**
* 合计运行时间
*/
private String runTime;
/**
* 运行版本
*/
private String runVersion;
/**
* 系统版本
*/
private String sysVersion;
/**
* 设备型号(手机)
*/
private String typeInfo;
/**
* 用例总数
*/
private String total;
/**
* 成功数据
*/
private String successTotal;
/**
* 失败数
*/
private String failedTotal;
/**
* 跳过
*/
private String skippedTotal;
/**
* 错误数
*/
private String errorTotal;
/**
* 执行执行方法
*/
private String runMethod;
/**
* 状态
*/
private String status;
/**
* 详情
*/
private String descriptionInfo;
/**
* 持续时间
*/
private String duration;
/**
* 日志
*/
private String detail;
ReportUtil
/**
* @author liwen406
* @Title: ReportUtil
* @Description: 报表工具类
* @date 2019/9/19 / 17:34
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
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 list = new ArrayList(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 list = new ArrayList(stackTrace.length);
for (Object o : stackTrace) {
list.add(renderArgument(o));
}
return commaSeparate(list);
}
/**
* 获取全部日志输出信息
*
* @return
*/
public List getAllOutput() {
return Reporter.getOutput();
}
/**
* 按testresult获取日志输出信息
*
* @param result
* @return
*/
public List 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 strings) {
StringBuilder buffer = new StringBuilder();
Iterator iterator = strings.iterator();
while (iterator.hasNext()) {
String string = iterator.next();
buffer.append(string);
if (iterator.hasNext()) {
buffer.append(", ");
}
}
return buffer.toString();
}
TestResult
/**
* @author liwen406
* @Title: TestResult
* @Description: 用于存储测试结果
* @date 2019/9/19 / 17:28
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class TestResult {
//测试方法名
private String testName;
//测试类名
private String className;
private String caseName;
//测试用参数
private String params;
//测试描述
private String description;
//Reporter Output
private String output;
private List twooutparam ;
//测试异常原因
private Throwable throwable;
private String throwableTrace;
//状态
private int status;
private String duration;
private boolean success;
TestResultCollection
/**
* @author liwen406
* @Title: TestResultCollection
* @Description: testng采用数据驱动,一个测试类可以有多个测试用例集合,每个测试类,应该有个测试结果集
* @date 2019/9/19 / 17:31
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
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 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;
}
监听器代码
ReporterListener.class
import cases.startdemo.utils.SendMailUtils;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSONObject;
import com.jd.dhf.api.test.util.LogUtil;
import com.jd.fastjson.JSON;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.AndroidElement;
import jdth.getsku.dao.imp.ReportUiDaoImpl;
import jdth.getsku.pojo.ReportUi;
import jdth.getsku.util.Log;
import jdth.getsku.util.OperationalCmd;
import jdth.getsku.util.WaitUtil;
import jdth.global.pagatest.BestRuner;
import org.apache.commons.io.FileUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.openqa.selenium.OutputType;
import org.testng.*;
import org.testng.annotations.Test;
import org.testng.xml.XmlSuite;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @author liwen
* @Title: ReporterListener
* @Description: 监听类 UI自动化测试报告
* @date 2019/9/19 / 17:33
*/
public class ReporterListener implements IReporter, ITestListener {
DateFormat dateFormat = new SimpleDateFormat("yyyy_MMdd_hhmmss");
/**
* 保存数据
*/
ReportUiDaoImpl reportUiDao = new ReportUiDaoImpl();
@Override
public void generateReport(List xmlSuites, List suites, String outputDirectory) {
List list = new LinkedList<>();
Date startDate = new Date();
Date endDate = new Date();
int TOTAL = 0;
int SUCCESS = 1;
int FAILED = 0;
int ERROR = 0;
int SKIPPED = 0;
for (ISuite suite : suites) {
Map 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 collections = this.parse(list);
VelocityContext context = new VelocityContext();
context.put("TOTAL", TOTAL);
context.put("mobileModel", OperationalCmd.getMobileModel());
context.put("versionName", OperationalCmd.getVersionNameInfo());
context.put("SUCCESS", SUCCESS);
context.put("FAILED", FAILED);
context.put("ERROR", ERROR);
context.put("SKIPPED", SKIPPED);
context.put("startTime", ReportUtil.formatDate(startDate.getTime()) + "<--->" + ReportUtil.formatDate(endDate.getTime()));
context.put("DURATION", ReportUtil.formatDuration(endDate.getTime() - startDate.getTime()));
context.put("results", collections);
write(context, outputDirectory);
//通过post请求
ReportTotal reportTotal = new ReportTotal();
reportTotal.setTestUser(System.getProperty("user.name"));
reportTotal.setStartTime(startDate + "");
reportTotal.setEndTime(endDate + "");
reportTotal.setRunTime(ReportUtil.formatDuration(endDate.getTime() - startDate.getTime()));
reportTotal.setRunVersion(OperationalCmd.getVersionNameInfo());
reportTotal.setSysVersion(OperationalCmd.getVersionNameInfo());
reportTotal.setTypeInfo(OperationalCmd.getMobileModel());
reportTotal.setTotal(TOTAL + "");
reportTotal.setSuccessTotal(SUCCESS + "");
reportTotal.setFailedTotal(FAILED + "");
reportTotal.setSkippedTotal(SKIPPED + "");
// String s = JSONObject.toJSONString(reportTotal);
// HttpRequest.post("http://127.0.0.1:8081/report/inserttotal").body(s).execute().body();
}
/**
* 输出模板
*
* @param context
* @param outputDirectory
*/
private void write(VelocityContext context, String outputDirectory) {
String fileDir = ReporterListener.class.getResource("/Template").getPath();
String reslutpath = outputDirectory + "/html/report" + dateFormat.format(new Date()) + ".html";
try {
//写文件
VelocityEngine ve = new VelocityEngine();
Properties p = new Properties();
p.setProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, fileDir);
p.setProperty(Velocity.ENCODING_DEFAULT, "utf-8");
p.setProperty(Velocity.INPUT_ENCODING, "utf-8");
ve.init(p);
Template t = ve.getTemplate("report.vm");
//输出结果
OutputStream out = new FileOutputStream(new File(reslutpath));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
// 转换输出
t.merge(context, writer);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
/**
* 发送邮件
*/
// SendMailUtils.sendemali(reslutpath);
}
private void sort(List list) {
Collections.sort(list, new Comparator() {
@Override
public int compare(ITestResult r1, ITestResult r2) {
if (r1.getStatus() < r2.getStatus()) {
return 1;
} else {
return -1;
}
}
});
}
private LinkedList listTestResult(IResultMap resultMap) {
Set results = resultMap.getAllResults();
return new LinkedList<>(results);
}
private Map parse(List list) {
Map 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();
// testResult.setParams(params + "");
// if (params != null && params.length >= 1) {
// String caseId = (String) params[0];
// LogUtil.info("caseid" + caseId);
// testResult.setCaseName(caseId);
// } else {
// testResult.setCaseName("null");
// }
testResult.setTestName(t.getMethod().getMethodName());
testResult.setParams(System.getProperty("user.name"));
testResult.setClassName(t.getTestClass().getName());
// testResult.setParams(ReportUtil.getParams(t));
//获取注解上面的 testName
testResult.setCaseName(t.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).testName());
testResult.setDescription(t.getMethod().getDescription());
testResult.setStatus(t.getStatus());
testResult.setThrowableTrace("class: " + t.getTestClass().getName() + "
method: " + t.getName() + "
error: " + t.getThrowable());
testResult.setThrowable(t.getThrowable());
long duration = t.getEndMillis() - t.getStartMillis();
testResult.setDuration(ReportUtil.formatDuration(duration));
testResult.setTwooutparam(Reporter.getOutput(t));
List output = Reporter.getOutput(t);
StringBuffer stringBuffer = new StringBuffer();
for (String s : output) {
stringBuffer.append(s + ",");
}
testResult.setOutput(stringBuffer.toString());
// String s = JSONObject.toJSONString(testResult);
// HttpRequest.post("http://127.0.0.1:8081/report/insertreport").body(s).execute().body();
return testResult;
}
/**
* 每次调用测试@Test之前调用
*
* @param result
*/
@Override
public void onTestStart(ITestResult result) {
logTestStart(result);
}
/**
* 用例执行结束后,用例执行成功时调用
*
* @param result
*/
@Override
public void onTestSuccess(ITestResult result) {
ReportUi reportUi = new ReportUi();
//开始时间
reportUi.setStartTime(ReportUtil.formatDate(result.getStartMillis()));
reportUi.setStatus("1");
//结束时间
reportUi.setEndtime(ReportUtil.formatDate(result.getEndMillis()));
//成功数据
reportUi.setSuccess("成功");
//运行用例类名字
reportUi.setRunMethodName(result.getName());
//描述
reportUi.setDescriptionInfo(result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).description());
reportUi.setTypeInfo(result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).testName());
LogUtil.info("查看是否获取数据:" + result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).testName());
//持续时间
reportUi.setDuration(String.valueOf(result.getEndMillis() - result.getStartMillis()) + "毫秒");
//日志
List output = Reporter.getOutput(result);
StringBuffer stringBuffer = new StringBuffer();
for (String s : output) {
stringBuffer.append(s + "
");
}
reportUi.setDetail(stringBuffer.toString());
reportUiDao.savereport(reportUi);
logTestEnd(result, "Success");
}
/**
* 用例执行结束后,用例执行失败时调用
* 跑fail则截图 获取屏幕截图
*
* @param result
*/
@Override
public void onTestFailure(ITestResult result) {
WaitUtil.sleep(2000);
AndroidDriver driver = BestRuner.getDriver();
File srcFile = driver.getScreenshotAs(OutputType.FILE);
File location = new File("./test-output/html/result/screenshots");
if (!location.exists()) {
location.mkdirs();
}
String dest = result.getMethod().getRealClass().getSimpleName() + "." + result.getMethod().getMethodName();
String s = dest + "_" + dateFormat.format(new Date()) + ".png";
File targetFile =
new File(location + "/" + s);
LogUtil.info("截图位置:");
Reporter.log("截图位置
" + targetFile.getPath());
LogUtil.info("------file is ---- " + targetFile.getPath());
try {
FileUtils.copyFile(srcFile, targetFile);
} catch (IOException e) {
e.printStackTrace();
}
logTestEnd(result, "Failed");
//报告截图后面显示
Reporter.log("");
ReportUi reportUi = new ReportUi();
//开始时间
reportUi.setStartTime(ReportUtil.formatDate(result.getStartMillis()));
//结束时间
reportUi.setEndtime(ReportUtil.formatDate(result.getEndMillis()));
reportUi.setStatus("0");
//成功数据
reportUi.setFailed("失败");
//运行用例类名字
reportUi.setRunMethodName(result.getName());
//描述
reportUi.setDescriptionInfo(result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).description());
reportUi.setTypeInfo(result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).testName());
//持续时间
reportUi.setDuration(String.valueOf(result.getEndMillis() - result.getStartMillis()) + "毫秒");
//日志
List output = Reporter.getOutput(result);
StringBuffer stringBuffer = new StringBuffer();
for (String s1 : output) {
stringBuffer.append(s1 + "
");
}
stringBuffer.append("+截图路径:" + targetFile.getPath());
reportUi.setDetail(stringBuffer.toString());
reportUiDao.savereport(reportUi);
}
/**
* 用例执行结束后,用例执行skip时调用
*
* @param result
*/
@Override
public void onTestSkipped(ITestResult result) {
ReportUi reportUi = new ReportUi();
//开始时间
reportUi.setStartTime(ReportUtil.formatDate(result.getStartMillis()));
//结束时间
reportUi.setEndtime(ReportUtil.formatDate(result.getEndMillis()));
reportUi.setStatus("2");
//成功数据
reportUi.setSkipped("跳过");
//运行用例类名字
reportUi.setRunMethodName(result.getName());
//描述
reportUi.setDescriptionInfo(result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).description());
reportUi.setTypeInfo(result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).testName());
//持续时间
reportUi.setDuration(String.valueOf(result.getEndMillis() - result.getStartMillis()) + "毫秒");
//日志
List output = Reporter.getOutput(result);
StringBuffer stringBuffer = new StringBuffer();
for (String s : output) {
stringBuffer.append(s + "
");
}
reportUi.setDetail(stringBuffer.toString());
reportUiDao.savereport(reportUi);
logTestEnd(result, "Skipped");
}
/**
* 每次方法失败但是已经使用successPercentage进行注释时调用,并且此失败仍保留在请求的成功百分比之内。
*
* @param result
*/
@Override
public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
logTestEnd(result, "FailedButWithinSuccessPercentage");
}
/**
* 在测试类被实例化之后调用,并在调用任何配置方法之前调用。
*
* @param context
*/
@Override
public void onStart(ITestContext context) {
return;
}
/**
* 在所有测试运行之后调用,并且所有的配置方法都被调用
*
* @param context
*/
@Override
public void onFinish(ITestContext context) {
return;
}
/**
* 在用例执行结束时,打印用例的执行结果信息
*/
protected void logTestEnd(ITestResult tr, String result) {
Reporter.log(String.format("=============Result: %s=============", result), true);
}
/**
* 在用例开始时,打印用例的一些信息,比如@Test对应的方法名,用例的描述等等
*/
protected void logTestStart(ITestResult tr) {
Reporter.log(String.format("=============Run: %s===============", tr.getMethod().getMethodName()), true);
Reporter.log(String.format("用例描述: %s, 优先级: %s", tr.getMethod().getDescription(), tr.getMethod().getPriority()),
true);
return;
}
}
报告模板
注意存放路径
report.vm
UI自动
泰国站UI自动化回归报告
汇总信息
开始与结束时间
${startTime}
执行时间
$DURATION seconds
运行版本与系统版本
${versionName}
设备型号
${mobileModel}
TOTAL
SUCCESS
FAILED
ERROR
SKIPPED
$TOTAL
$SUCCESS
$FAILED
$ERROR
$SKIPPED
详情
#foreach($result in $results.entrySet())
#set($item = $result.value)
测试类
$result.key
TOTAL: $item.totalSize
SUCCESS: $item.successSize
FAILED: $item.failedSize
ERROR: $item.errorSize
SKIPPED: $item.skippedSize
Status
Method
Description
Duration
Detail
#foreach($testResult in $item.resultList)
#if($testResult.status==1)
success
#elseif($testResult.status==2)
failure
#elseif($testResult.status==3)
skipped
#end
$testResult.testName
${testResult.description}
${testResult.duration} seconds
log
#end
#end
Android前端UI自动化