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,可以看到相關數據,詳見下圖