一键测试生成单元测试报告testNG+BeautifulReport
一.背景
1.公司为规范团队的开发,涉及到组下员工的代码开发规范,日志打印规范,预警机制等。其中一项是规范团队的开发的单元测试。为了方便核检查小组开发需求的单元测试而整合的单元测试报告,测试框架用的测试框架为TESTNG,模板用的是BeautifulReport。先看一下展示的效果
二.搭建
1.使用的pom
这里我根据个人需求,把mongodb的包排除了,是因为extentreports包里面的一些异常信息截图功能会使用到mongodb,项目因为没有用到mongodb,这里不排除单侧会抛异常,但不影响使用。
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.14.3</version>
</dependency>
<dependency>
<groupId>com.aventstack</groupId>
<artifactId>extentreports</artifactId>
<version>3.0.7</version>
<exclusions>
<exclusion>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
</exclusion>
</exclusions>
</dependency>
2.监听器
我在项目下src/test/java/com/abcnull/listener添加了一个测试报告监听器类 TestReportListener,它继承自 testng 依赖包中的 IReporter。TestReportListener 中其实没用到太多 extentreports 依赖中的 API,只有 Gson 类来自 extentreports 依赖中,基本还是靠着第三步中写好的 BeautifulReport 型式的 html 模板,代码如下
监听器代码如下,代码可根据个人需求自行继续编写优化
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.testng.*;
import org.testng.xml.XmlSuite;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @author abcnull
* @version 1.0.0
* @date 2020/1/26
*/
public class TestReportListener implements IReporter {
// 日期格式化
private static Date date = new Date();
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd,HH点mm分ss秒");
private static String reportdate = simpleDateFormat.format(date);
private static String getReportName = "测试报告-" + reportdate;
// 定义html模板所在路径
private String templatePath = this.getClass().getResource("/").getPath() + "report/template.html";
// 定义报告生成的路径
private String reportDirPath = System.getProperty("user.dir") + File.separator + "target" + File.separator + "test-output" + File.separator + "report";
private String reportPath = reportDirPath + File.separator + getReportName + ".html";
private String name = "DemoTest";
private int testsPass;
private int testsFail;
private int testsSkip;
private String beginTime;
private long totalTime;
private String project = "单元测试报告";
@Override
public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
List<ITestResult> list = new ArrayList<ITestResult>();
for (ISuite suite : suites) {
Map<String, ISuiteResult> suiteResults = suite.getResults();
for (ISuiteResult suiteResult : suiteResults.values()) {
ITestContext testContext = suiteResult.getTestContext();
IResultMap passedTests = testContext.getPassedTests();
testsPass = testsPass + passedTests.size();
IResultMap failedTests = testContext.getFailedTests();
testsFail = testsFail + failedTests.size();
IResultMap skippedTests = testContext.getSkippedTests();
testsSkip = testsSkip + skippedTests.size();
IResultMap failedConfig = testContext.getFailedConfigurations();
list.addAll(this.listTestResult(passedTests));
list.addAll(this.listTestResult(failedTests));
list.addAll(this.listTestResult(skippedTests));
list.addAll(this.listTestResult(failedConfig));
}
}
this.sort(list);
this.outputResult(list);
}
private ArrayList<ITestResult> listTestResult(IResultMap resultMap) {
Set<ITestResult> results = resultMap.getAllResults();
return new ArrayList<ITestResult>(results);
}
private void sort(List<ITestResult> list) {
Collections.sort(list, new Comparator<ITestResult>() {
@Override
public int compare(ITestResult r1, ITestResult r2) {
return r1.getStartMillis() < r2.getStartMillis() ? -1 : 1;
}
});
}
public long getTime() {
return totalTime;
}
private void outputResult(List<ITestResult> list) {
try {
List<ReportInfo> listInfo = new ArrayList<ReportInfo>();
int index = 0;
for (ITestResult result : list) {
String testName = result.getTestContext().getCurrentXmlTest().getName();
if (index == 0) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmssSSS");
beginTime = formatter.format(new Date(result.getStartMillis()));
index++;
}
long spendTime = result.getEndMillis() - result.getStartMillis();
totalTime += spendTime;
String status = this.getStatus(result.getStatus());
List<String> log = Reporter.getOutput(result);
for (int i = 0; i < log.size(); i++) {
log.set(i, log.get(i).replaceAll("\"", "\\\\\""));
}
Throwable throwable = result.getThrowable();
if (throwable != null) {
log.add(throwable.toString().replaceAll("\"", "\\\\\""));
StackTraceElement[] st = throwable.getStackTrace();
for (StackTraceElement stackTraceElement : st) {
log.add((" " + stackTraceElement).replaceAll("\"", "\\\\\""));
}
}
ReportInfo info = new ReportInfo();
info.setName(testName);
info.setSpendTime(spendTime + "ms");
info.setStatus(status);
info.setClassName(result.getInstanceName());
info.setMethodName(result.getName());
info.setDescription(result.getMethod().getDescription());
info.setLog(log);
listInfo.add(info);
}
Map<String, Object> result = new HashMap<String, Object>();
result.put("testName", this.project);
result.put("testPass", testsPass);
result.put("testFail", testsFail);
result.put("testSkip", testsSkip);
result.put("testAll", testsPass + testsFail + testsSkip);
result.put("beginTime", beginTime);
result.put("totalTime", totalTime + "ms");
result.put("testResult", listInfo);
Gson gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create();
String template = this.read(reportDirPath, templatePath);
BufferedWriter output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(reportPath)), "UTF-8"));
template = template.replace("${resultData}", gson.toJson(result));
output.write(template);
output.flush();
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private String getStatus(int status) {
String statusString = null;
switch (status) {
case 1:
statusString = "成功";
break;
case 2:
statusString = "失败";
break;
case 3:
statusString = "跳过";
break;
default:
break;
}
return statusString;
}
public static class ReportInfo {
private String name;
private String className;
private String methodName;
private String description;
private String spendTime;
private String status;
private List<String> log;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public String getSpendTime() {
return spendTime;
}
public void setSpendTime(String spendTime) {
this.spendTime = spendTime;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public List<String> getLog() {
return log;
}
public void setLog(List<String> log) {
this.log = log;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
private String read(String reportDirPath, String templatePath) {
//文件夹不存在时级联创建目录
File reportDir = new File(reportDirPath);
if (!reportDir.exists() && !reportDir.isDirectory()) {
reportDir.mkdirs();
}
File templateFile = new File(templatePath);
InputStream inputStream = null;
StringBuffer stringBuffer = new StringBuffer();
try {
inputStream = new FileInputStream(templateFile);
int index = 0;
byte[] b = new byte[1024];
while ((index = inputStream.read(b)) != -1) {
stringBuffer.append(new String(b, 0, index));
}
return stringBuffer.toString();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
3.在src/resources下新增testNG.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="单元测试报告">
<!-- 添加html监听,这里指向监听器的包路径-->
<listeners>
<listener class-name="com.epos.sqdz.listener.TestReportListener"/>
</listeners>
<!-- 指定某个或某些单元测试类运行-->
<test verbose="2" preserve-order="true" name="测试登录单元测试">
<!--传递参数-->
<parameter name="add1" value="3"/>
<parameter name="add2" value="2"/>
<parameter name="result" value="6"/>
<classes>
<class name="com.epos.sqdz.service.impl.TestNG2" />
</classes>
</test>
<!-- <!– 1.指定单元测试类里面的某个或某些方法运行。-->
<!-- 2. preserve-order 参数用于控制测试用例的执行顺序。如果为:true,-->
<!-- 测试用例的顺序为:testCase > testCase1 > testCase2。-->
<!-- 如果为:false ,那么默认会按照用例的名称的有字母/数字的顺序执行:testCase1 > testCase2 > testCase3。-->
<!-- –>-->
<!-- <test verbose="2" preserve-order="true" name="测试执行顺序">-->
<!-- <classes>-->
<!-- <class name="com.epos.sqdz.service.impl.TestNG1" >-->
<!-- <methods>-->
<!-- <include name="testCase3" />-->
<!-- <include name="testCase1" />-->
<!-- <include name="testCase2" />-->
<!-- </methods>-->
<!-- </class>-->
<!-- </classes>-->
<!-- </test>-->
<!-- <!– 测试传参–>-->
<!-- <test verbose="2" preserve-order="true" name="测试单元测试传参">-->
<!-- <parameter name="add1" value="3"/>-->
<!-- <parameter name="add2" value="2"/>-->
<!-- <parameter name="result" value="6"/>-->
<!-- <classes>-->
<!-- <class name="com.epos.sqdz.service.impl.TestNG1" >-->
<!-- <methods>-->
<!-- <include name="testAdd1" />-->
<!-- </methods>-->
<!-- </class>-->
<!-- </classes>-->
<!-- </test>-->
</suite>
4.编写单元测试
import com.epos.sqdz.EposApiApplication;
import com.epos.sqdz.entity.UserBankCardBind;
import com.epos.sqdz.mapper.UserBankCardBindMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
import org.springframework.transaction.annotation.Transactional;
import org.testng.Assert;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
import java.util.List;
@SpringBootTest(classes = EposApiApplication.class)
public class TestNG2 extends AbstractTransactionalTestNGSpringContextTests {
@Autowired
private UserBankCardBindMapper userBankCardBindMapper;
@Test(description = "比较两数相等")
public void testA() {
Assert.assertEquals(1, 1);
}
@Test(description = "测试查询数据库")
public void testB() {
List<UserBankCardBind> bindInfo = userBankCardBindMapper.selectCardInfoByAuthCard("6228430338035659874", "1000000422");
Assert.assertEquals(1, 1);
}
@Test(description = "测试插入数据库")
@Transactional
@Rollback(true)// 事务自动回滚,默认是true。可以不写
public void testC() {
UserBankCardBind userBankCardBind = new UserBankCardBind();
userBankCardBind.setAccountCode("-");
userBankCardBind.setAccountName("-");
userBankCardBind.setAccountType("-");
userBankCardBind.setAgentCode("-");
userBankCardBind.setAgentName("-");
userBankCardBind.setBankCity("-");
userBankCardBind.setBankCode("-");
userBankCardBind.setBankName("-");
userBankCardBind.setBankProvice("-");
userBankCardBind.setChangeType("-");
userBankCardBind.setMhtType("-");
userBankCardBind.setOldAccountCode("-");
userBankCardBind.setOldAccountName("-");
userBankCardBind.setUserId(452451L);
userBankCardBind.setRecordId(2L);
int num = userBankCardBindMapper.insert(userBankCardBind);
Assert.assertEquals(num, 1);
}
@Test(description = "测试方法传参")
@Parameters({"add1","add2","result"})
public void testAdd1(int add1, int add2, int result){
Assert.assertEquals(add1+ add2, result);
}
}
5.创建html模板
1.模板内容较长,免费免积分下载地址:https://download.csdn.net/download/weixin_43659676/14121325
2.在src/test/resouces/report下创建一个html模板,路径可自定义,搭配调整监听器路径即可,模板内容也可根据需求自定义优化编写
三.使用
直接右键运行xml,便会执行所有单元测试,执行完成后,在target/tets-output/report(路径在监听器可调整)下会生成一个html测试报告,就可以直接打开使用了
本文地址:https://blog.csdn.net/weixin_43659676/article/details/112503490
推荐阅读
-
一键测试生成单元测试报告testNG+BeautifulReport
-
Python Selenium框架设计使自动化测试报告的生成
-
Windows下wamp php单元测试工具PHPUnit安装及生成日志文件配置方法
-
使用PHPUnit进行单元测试并生成代码覆盖率报告的方法
-
基于Jmeter生成测试报告过程图解
-
基于python语言的自动化测试中生成html的测试报告时HtmlTestRunner模块常见问题
-
Python单元测试介绍及单元测试理解,单元测试的自动生成
-
详解Appium+Python之生成html测试报告
-
Java和.Net版通用工具类实现--生成自定义Web Html/Excel测试用例和测试报告
-
Python3 + Appium + 安卓模拟器实现APP自动化测试并生成测试报告