一、Testng之监听器
Testng监听器的父级接口为ITestNGListener,然后定义了各种监听器接口继承于ITestNGListener接口,然后下图为常用的几种监听器。
二、Testng监听器调用方式
2.1 XML中调用监听器
Testng中通过带有suite标签的xml来配置所有测试方法,然后再suite标签中使用listeners和listener标签,就可以使用监听器。代码如下:
<suite name="All Suite" parallel="tests" thread-count="1" >
<!--suite-files标签,将Test分在多个suite中-->
<suite-files>
<suite-file path="zhangxiong_testng.xml"/>
<suite-file path="yangfanfan_testng.xml"/>
</suite-files>
<listeners>
<listener class-name="com.yff.listener.TestLogListener"/>
<listener class-name="com.yff.listener.TestReportListener"/>
</listeners>
</suite>
2.2 类中调用监听器
除了在testng.xml中配置监听器,也可以在指定的测试类中使用@Listeners注解来指定测试类中使用的监听器。
@Listeners({TestLogListener.class, TestReportListener.class})
public class TestngDemo {
@Test()
public void test1(){
System.out.println("Test");
}
}
三、Testng监听器实现案例
3.1 ITestListener实现案例
实现大体思路:监听器类继承ITestListener的实现类TestListenerAdapter,并重写onStart、onTestStart、onTestSuccess、onTestFailure、onTestSkipped、onFinish方法。
然后再结合ITestContext、ITestResult类型的参数去实现相关的输出及操作(例如UI自动化中失败后截图)。
@Slf4j
public class TestLogListener extends TestListenerAdapter {
@Override
public void onStart(ITestContext iTestContext) {
super.onStart( iTestContext );
log.info( String.format( "====================%s测试开始====================", iTestContext.getName() ) );
}
@Override
public void onTestStart(ITestResult iTestResult) {
super.onTestStart( iTestResult );
log.info( String.format( "========%s.%s测试开始========", iTestResult.getInstanceName(), iTestResult.getName()) );
}
@Override
public void onTestSuccess(ITestResult iTestResult) {
super.onTestSuccess( iTestResult );
log.info( String.format( "========%s.%s测试通过========", iTestResult.getInstanceName(), iTestResult.getName()) );
}
@Override
public void onTestFailure(ITestResult iTestResult) {
WebDriver driver = BaseTest.driverBase.getDriver();
System.out.print( "report_driver_fail:" + driver );
super.onTestFailure( iTestResult );
log.error( String.format( "========%s.%s测试失败,失败原因如下:\n%s========", iTestResult.getInstanceName(), iTestResult.getName(), iTestResult.getThrowable() ));
// 失败截图
ScreenShot.takeScreenShot( driver,iTestResult);
}
@Override
public void onTestSkipped(ITestResult iTestResult) {
super.onTestSkipped( iTestResult );
log.info( String.format( "========%s.%s跳过测试========", iTestResult.getInstanceName(), iTestResult.getName()) );
}
@Override
public void onFinish(ITestContext iTestContext) {
super.onFinish( iTestContext );
log.info( String.format( "====================%s测试结束====================", iTestContext.getName() ) );
}
}
3.2 IReporter实现案例
实现思路:利用重写IReporter中的方法generateReport,读取执行的结果,然后这里将UI自动化执行的结果存储在List集合中,并将集合中的信息写入到报告模板中,最后生成测试报告。
报告实例:报告.html
@Slf4j
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 = "HCF_WebUI_TestReport" + 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 int testsPass;
private int testsFail;
private int testsSkip;
private String beginTime;
private long totalTime;
private String project = "WebUI自动化测试报告";
/**
* 获取各个xml中的执行结果并调用其他方法将结果写入到报告中
* @param xmlSuites 各个xmlSuite
* @param suites 各个suites结果
* @param outputDirectory 输出目录
*/
@Override
public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
List<ITestResult> list = new ArrayList<>();
for (ISuite suite : suites) {
/*这里可以理解为每个suite对应一个xml,其中testng也算一个。*/
log.info("suite的名称"+suite.getName());
/*获取每个suite的Results,也就是每个suite中的功能点,testng获取的就是一个空的*/
Map<String, ISuiteResult> suiteResults = suite.getResults();
log.info("suiteResults的长度:"+suiteResults.size());
for(String s:suiteResults.keySet()){
log.info("测试功能名称:"+s);
ISuiteResult suiteResult=suiteResults.get(s);
log.info(suiteResult.toString());
/*获取该测试功能点的测试结果*/
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));
}
}
log.info("list集合的长度"+list.size());
this.sort(list);
this.outputResult(list);
}
/**
* 将读出来的list结果输入到报告中
* @param list 结果的list集合
*/
private void outputResult(List<ITestResult> list) {
try {
/*定义listInfo集合,存储每个用例的执行结果*/
List<ReportInfo> listInfo = new ArrayList<>();
/*定义起始的编号*/
int index = 0;
/*循环list,每个result对象对应一个测试用例*/
for (ITestResult result : list) {
String testName = result.getTestContext().getCurrentXmlTest().getName();
log.info("testName的值"+testName);
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对应,存储一个测试用例的信息*/
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中*/
listInfo.add(info);
}
/*此处对应测试结果的大致数据,对应测试报告中上部分的汇总报告*/
Map<String, Object> result = new HashMap<>();
log.info("总运行时间为:"+totalTime);
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");
/*将上面每个测试用例的测试结果信息的listInfo存储在result的Map中*/
result.put("testResult", listInfo);
/*定义gson对象*/
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();
}
}
/**
* 创建报告生成目录并读取报告模板中的文本并返回报告模板中的字符串
* @param reportDirPath 报告生成目录
* @param templatePath 报告模板文件路径
* @return 返回字符串
*/
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;
}
/**
* 用于对测试类中的每个类中得到测试结果转换为List集合
* @param resultMap 测试类测试结果映射的集合
* @return 返回结果的List集合
*/
private ArrayList<ITestResult> listTestResult(IResultMap resultMap) {
Set<ITestResult> results = resultMap.getAllResults();
return new ArrayList<ITestResult>(results);
}
/**
* 定义的静态内部类,类的属性对应生成报告里面的显示字段。 显示字段分别为 :
* name(用例名称) className(测试类) method(测试方法) description(描述)
* spendTime(花费时间) status(结果状态) log(日志)
*/
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;
}
}
/**
* 获取运行的总时间
* @return 返回totalTime的值
*/
public long getTime(){
return totalTime;
}
/**
* 获取执行结果状态对应的字符串
* @param status 结果状态数字 1、2、3
* @return 结果状态的字符串 成功、失败、跳过
*/
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;
}
/**
* 排序方法 对list进行排序
* @param list 排序的list列表
*/
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;
}
});
}
}
3.3 IAnnotationTransformer实现案例
实现思路:循环Class中的Test方法,并给方法的@Test注解设置priority的值,以免后续执行顺序出现问题。
/*
@param annotation 从测试类中读取的注解
@param testClass 如果在类上找到了注释,则此参数表示此类(否则为null)
@param testConstructor 如果在构造函数上找到注释,则此参数表示此构造函数(否则为null)
@param testMethod 如果在方法上找到注释,则此参数表示此方法(否则为null)。
*/
public void transform(ITestAnnotation annotation, Class testClass,
Constructor testConstructor, Method testMethod);
public class RePrioritizingListener implements IAnnotationTransformer {
//创建的Map键值对,键为类的Class属性,值为Integer值
HashMap<Object, Integer> priorityMap = new HashMap<Object, Integer>();
//定义的Class类的起始值
Integer class_priorityCounter = 10000;
//设置Test注解的属性priority最大长度为4
Integer max_testpriorityLength = 4;
//我们这里是在方法上添加的Test注解,所以这里非空的参数为testMethod
public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
//获取方法所属的Class类
Class<?> declaringClass = testMethod.getDeclaringClass();
//获取Test注解的属性priority的值
Integer test_priority = annotation.getPriority();
//获取对应Class键对应的值
Integer current_ClassPriority = priorityMap.get(declaringClass);
//如果Map对象中没有该类,就赋值为class_priorityCounter并加1
if (current_ClassPriority == null) {
current_ClassPriority = class_priorityCounter++;
priorityMap.put(declaringClass, current_ClassPriority);
}
//将Test注解的属性priority的值在前面补充0,直到长度达到4
String concatenatedPriority = test_priority.toString();
while (concatenatedPriority.length() < max_testpriorityLength) {
concatenatedPriority = "0" + concatenatedPriority;
}
//将Class对应的值和Test注解的属性priority值凭借起来
concatenatedPriority = current_ClassPriority.toString() + concatenatedPriority;
/*设置注解的priority值*/
annotation.setPriority(Integer.parseInt(concatenatedPriority));
//拼接字符串,并用Reporter.log输出
String printText = testMethod.getName() + " Priority = " + concatenatedPriority;
Reporter.log(printText);
System.out.println(printText);
}
}