ExtentReports 的简单使用
1. 简介
简单来说 ExtentReports 就是一个测试报告框架,可以简单的应用到我们日常的单元测试,接口测试等中。同时它还提供了 测试报告服务器 klov。
官网上有这段话:
ExtentReports is an logger-style reporting library for automated tests. A logger is simply an object to log messages or events for a specific system or application. ExtentReports uses the logging style to add information about test sessions, such as creation of tests, adding screenshots, assigning tags, and adding events or series of steps to sequentially indicate the flow of test steps.
翻译过来:
ExtentReports 是一个用于生成自动化测试报告的库。报告中会详细记录系统或应用程序的消息或事件对象。ExtentReports 可以添加有关测试会话的信息,例如创建测试、添加屏幕截图、分配标记,以及添加事件或一系列步骤,以顺序指示测试步骤的流程。支持两种语言JAVA,NET,ExtentReports有个很重要的组件 KLOV报表服务器,它提供了对历史报告的数据分析。
客户端:https://github.com/extent-framework/extentreports-java, DOC
服务端:https://github.com/extent-framework/klov,Doc
2. ExtentReports java 实现
2.1 在pom.xml文件中配置依赖
<dependency>
<groupId>com.aventstack</groupId>
<artifactId>extentreports</artifactId>
<version>5.0.8</version>
</dependency>
2.2 在工程目录下添加两个类
监听类 ExtentTestNGIReporterListener 用于实现 testng 的 IReporter 接口,
ExtentManager 用于生成 ExtentReports。
import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.Status;
import org.testng.*;
import org.testng.xml.XmlSuite;
import java.util.*;
/**
* @author nan
* @title: ExtentTestertNgFormat
* @projectName ISTP_new_dev
* @description: TODO
* @date 2021/11/249:42
*/
public class ExtentTestNGIReporterListener implements IReporter {
private ExtentReports extent = ExtentManager.getInstance();
@Override
public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
boolean createSuiteNode = false;
if (suites.size() > 1) {
createSuiteNode = true;
}
for (ISuite suite : suites) {
Map<String, ISuiteResult> result = suite.getResults();
//如果suite里面没有任何用例,直接跳过,不在报告里生成
if (result.size() == 0) {
continue;
}
//统计suite下的成功、失败、跳过的总用例数
int suiteFailSize = 0;
int suitePassSize = 0;
int suiteSkipSize = 0;
ExtentTest suiteTest = null;
//存在多个suite的情况下,在报告中将同一个suite的测试结果归为一类,创建一级节点。
if (createSuiteNode) {
suiteTest = extent.createTest(suite.getName()).assignCategory(suite.getName());
}
boolean createSuiteResultNode = false;
if (result.size() > 1) {
createSuiteResultNode = true;
}
for (ISuiteResult r : result.values()) {
ExtentTest resultNode;
ITestContext context = r.getTestContext();
if (createSuiteResultNode) {
//没有创建suite的情况下,将在SuiteResult的创建为一级节点,否则创建为suite的一个子节点。
if (null == suiteTest) {
resultNode = extent.createTest(r.getTestContext().getName());
} else {
resultNode = suiteTest.createNode(r.getTestContext().getName());
}
} else {
resultNode = suiteTest;
}
if (resultNode != null) {
resultNode.getModel().setName(suite.getName() + " : " + r.getTestContext().getName());
if (resultNode.getModel().hasCategory()) {
resultNode.assignCategory(r.getTestContext().getName());
} else {
resultNode.assignCategory(suite.getName(), r.getTestContext().getName());
}
resultNode.getModel().setStartTime(r.getTestContext().getStartDate());
resultNode.getModel().setEndTime(r.getTestContext().getEndDate());
//统计SuiteResult下的数据
int passSize = r.getTestContext().getPassedTests().size();
int failSize = r.getTestContext().getFailedTests().size();
int skipSize = r.getTestContext().getSkippedTests().size();
suitePassSize += passSize;
suiteFailSize += failSize;
suiteSkipSize += skipSize;
if (failSize > 0) {
resultNode.getModel().setStatus(Status.FAIL);
}
resultNode.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;", passSize, failSize, skipSize));
}
buildTestNodes(resultNode, context.getFailedTests(), Status.FAIL);
buildTestNodes(resultNode, context.getSkippedTests(), Status.SKIP);
buildTestNodes(resultNode, context.getPassedTests(), Status.PASS);
}
if (suiteTest != null) {
suiteTest.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;", suitePassSize, suiteFailSize, suiteSkipSize));
if (suiteFailSize > 0) {
suiteTest.getModel().setStatus(Status.FAIL);
}
}
}
extent.flush();
}
private void buildTestNodes(ExtentTest extenttest, IResultMap tests, Status status) {
//存在父节点时,获取父节点的标签
String[] categories = new String[0];
if (extenttest != null) {
/* List<TestAttribute> categoryList = extenttest.getModel().getCategoryContext().getAll();
categories = new String[categoryList.size()];
for(int index=0;index<categoryList.size();index++){
categories[index] = categoryList.get(index).getName();
}*/
}
ExtentTest test;
if (tests.size() > 0) {
//调整用例排序,按时间排序
Set<ITestResult> treeSet = new TreeSet<ITestResult>(new Comparator<ITestResult>() {
@Override
public int compare(ITestResult o1, ITestResult o2) {
return o1.getStartMillis() < o2.getStartMillis() ? -1 : 1;
}
});
treeSet.addAll(tests.getAllResults());
for (ITestResult result : treeSet) {
Object[] parameters = result.getParameters();
String name = "";
//如果有参数,则使用参数的toString组合代替报告中的name
for (Object param : parameters) {
name += param.toString();
}
if (name.length() > 0) {
if (name.length() > 50) {
name = name.substring(0, 49) + "...";
}
} else {
name = result.getMethod().getMethodName();
}
if (extenttest == null) {
test = extent.createTest(name);
} else {
//作为子节点进行创建时,设置同父节点的标签一致,便于报告检索。
test = extenttest.createNode(name).assignCategory(categories);
}
for (String group : result.getMethod().getGroups())
test.assignCategory(group);
List<String> outputList = Reporter.getOutput(result);
for (String output : outputList) {
//将用例的log输出报告中
//test.debug(output);
}
if (result.getThrowable() != null) {
test.log(status, result.getThrowable());
} else {
test.log(status, "Test " + status.toString().toLowerCase() + "ed");
}
test.getModel().setStartTime(getTime(result.getStartMillis()));
test.getModel().setEndTime(getTime(result.getEndMillis()));
}
}
}
private Date getTime(long millis) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(millis);
return calendar.getTime();
}
}
import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.reporter.ExtentKlovReporter;
import com.aventstack.extentreports.reporter.ExtentSparkReporter;
import com.aventstack.extentreports.reporter.configuration.ExtentSparkReporterConfig;
import com.aventstack.extentreports.reporter.configuration.Theme;
import java.io.File;
/**
* @author nan
* @title: ExtentManager
* @projectName ISTP_new_dev
* @description: TODO
* @date 2021/11/2510:51
*/
public class ExtentManager {
//生成的路径以及文件名
private static final String OUTPUT_FOLDER = "test-output/";
private static final String FILE_NAME = "index5.html";
private static ExtentReports extent;
public static ExtentReports getInstance() {
if (extent == null)
createInstance();
return extent;
}
public static void createInstance() {
//文件夹不存在的话进行创建
File reportDir = new File(OUTPUT_FOLDER);
if (!reportDir.exists() && !reportDir.isDirectory()) {
reportDir.mkdir();
}
extent = new ExtentReports();
extent.attachReporter(createSparkReporter(OUTPUT_FOLDER + FILE_NAME));
//extent.setSystemInfo("os", "Linux");
extent.setReportUsesManualConfiguration(true);
}
private static ExtentSparkReporter createSparkReporter(String filePath) {
ExtentSparkReporter spark = new ExtentSparkReporter(filePath);
spark.config(
ExtentSparkReporterConfig.builder()
.theme(Theme.DARK)
.reportName("ISTP API自动化测试报告")
.documentTitle("ISTP API自动化测试报告")
.build());
return spark;
}
}
2.3 在测试执行xml文件中配置监听
<?xml version="1.0" encoding="UTF-8" ?>
<suite name="这是我自己的接口测试套件">
<test name="这是测试模块">
<classes>
<class name="com.report.ReportTest"/>
<methods>
......
</methods>
</classes>
</test>
<listeners>
<!--这是配置生成报告的监听-->
<listener class-name="com..report.ExtentTestNGIReporterListener"/>
</listeners>
</suite>
2.4 运行测试,并查看报告
在报告输出路径下可以看到已生成的测试报告,详见下图。
3. ExtentReports klov 实现
3.1 Klov 安装
最新版官方提供的是Docker环境,由于目前手头没有 Docker 环境,我这里用了老版本 klov-0.1.1.jar ,jar包安装
1. 安装mongoDB并启动,参见
2. 安装redis并启动(可选)
3. 下载klov jar包,修改其配置文件 application.properties
# klov
application.name=Klov
server.host=localhost
server.port=8081
# data.mongodb
spring.data.mongodb.host=*
spring.data.mongodb.port=27017
spring.data.mongodb.database=klov
# data.rest
spring.data.rest.basePath=/rest
spring.data.rest.default-page-size=6
# redis, session
use.redis.session.store=false
spring.redis.host=*
spring.redis.port=6379
spring.redis.ssl=false
spring.redis.database=0
spring.session.store-type=redis
server.session.timeout=-1
# users
server.admin.name=admin
server.admin.key=$2a$10$I/5TFi6BrHChUghTZEZfCO82txzu8L5brcK0CxhS3m.V6glfj2vZe
# storage
file.storage.location=./upload/reports/
# schedulers
scheduler.jobs.enabled=false
scheduler.job.builds.retain.count=100
# mail,有这些设置才能重置忘记的密码。
spring.mail.host=
spring.mail.port=
spring.mail.username=
spring.mail.password=
spring.mail.properties.mail.smtp.ssl.enable=true
spring.mail.properties.mail.smtp.auth=true
spring.mail.test-connection=true
如未安装 Redis,只需在 application.properties 以下位置取消注释:
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.session.SessionAutoConfiguration
4. 启动Klov,并验证
java -jar klov-x.x.x.jar
打开http://127.0.0.1:8081/password,输入admin/password,即可登录,至此Klov服务端已经成功运行。
3.2 客户端测试接入 Klov 服务器
1. 在pom.xml文件中配置依赖
<properties>
<java.version>1.8</java.version>
<mongodb.version>3.6.4</mongodb.version>
</properties>
......
<!-- https://mvnrepository.com/artifact/com.aventstack/klov-reporter -->
<dependency>
<groupId>com.aventstack</groupId>
<artifactId>klov-reporter</artifactId>
<version>5.0.8</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
<version>${mongodb.version}</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>bson</artifactId>
<version>${mongodb.version}</version>
</dependency>
2. 在ExtentManager类中增加klov接入代码
public class ExtentManager {
//生成的路径以及文件名
private static final String OUTPUT_FOLDER = "test-output/";
private static final String FILE_NAME = "index5.html";
private static ExtentReports extent;
public static ExtentReports getInstance() {
if (extent == null)
createInstance();
return extent;
}
public static void createInstance() {
//文件夹不存在的话进行创建
File reportDir = new File(OUTPUT_FOLDER);
if (!reportDir.exists() && !reportDir.isDirectory()) {
reportDir.mkdir();
}
extent = new ExtentReports();
extent.attachReporter(createSparkReporter(OUTPUT_FOLDER + FILE_NAME), createKlovReporter());
//extent.setSystemInfo("os", "Linux");
extent.setReportUsesManualConfiguration(true);
}
private static ExtentSparkReporter createSparkReporter(String filePath) {
ExtentSparkReporter spark = new ExtentSparkReporter(filePath);
spark.config(
ExtentSparkReporterConfig.builder()
.theme(Theme.DARK)
.reportName("ISTP API自动化测试报告")
.documentTitle("ISTP API自动化测试报告")
.build());
return spark;
}
private static ExtentKlovReporter createKlovReporter() {
// 创建一个KlovReporter对象
ExtentKlovReporter klov = new ExtentKlovReporter("zuozewei-test");
klov
.initKlovServerConnection("10.16.55.95:8081")
.initMongoDbConnection("10.16.55.95", 27017);
return klov;
}
}
3. 运行测试,从标准输出可以看到Klov相关日志
4. 刷新Klov,可以看到相关数据,详见下图