昨天有个小伙伴问我,有没有什么现成的测试报告模板,由于昨天实在比较忙就没顾上,所以今个有时间赶紧补上。一般力所能及的事,只要我有时间都会为大家解决,但毕竟能力有限做不到的地方小伙伴们也多理解。
在这里插入图片描述
平时我们开发接口时,Junit 单元测试是最为常用的一种开发测试手段,很多时候测试其实只看接口是否正常返回结果就 ok 了。但有时间我们要测试一些特殊场景,如:接口超时测试等,就没什么太好的办法了,而 TestNG 实现容易的多。它与 JUnit 用法十分相似,只要你用过 JUnit 分分钟上手。
大致讲一下 TestNG 的几个重要概念,@Test 注解标注的方法是最小的执行单元,我们可以将这些单个的测试用例划分成 group 分组管理,group 可以用在测试类或者方法上,suite 套件可以理解成测试类的容器。
下边我们搭建一个TestNG测试框架,结合具体案例介绍一下它的功能。
核心依赖引入 extentreports 和 testng
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.aventstack</groupId>
<artifactId>extentreports</artifactId>
<version>3.0.6</version>
</dependency>
</dependencies>
TestNG 配置
TestNG 支持两种执行方式,第一种是用注解像 Junit 直接点方法名 run 执行。第二种配置 xml 文件的方式。
@Slf4j
@Listeners({ExtentTestNGIReporterListener.class})
@SpringBootTest(classes=SpringbootTestngReportApplication.class)
publicclassUserTestextendsAbstractTestNGSpringContextTests{
@Data
classUser{
privateIntegeruserId;
privateStringuserName;
}
/**
*参数提供
*/
@DataProvider(name="paramDataProvider")
publicObject[][]paramDataProvider(){
Useruser1=newUser();
user1.setUserId(1);
user1.setUserName("程序员内点事1");
Useruser2=newUser();
user2.setUserId(2);
user2.setUserName("程序员内点事2");
returnnewObject[][]{{1,user1},{2,user2}};
}
@Test(dataProvider="paramDataProvider")
publicvoidqueryUser(Integerindex,Useruser){
if(index==2){
inta=1/0;
}
log.info("index:{},user:{}",index,JSON.toJSONString(user));
Assert.assertTrue(Objects.nonNull(user));
}
}
xml 方式直接右键 .xml文件 run 就运行了。
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEsuiteSYSTEM"http://testng.org/testng-1.0.dtd">
<suitename="用户单元测试"parallel="classes"thread-count="5">
<listeners>
<listenerclass-name="com.xiaofu.report.config.ExtentTestNGIReporterListener"/>
</listeners>
<testverbose="1"name="用户测试">
<parametername="userId"value="1"/>
<parametername="userName"value="程序员内点事"/>
<groups>
<definename="queryUser"/>
<definename="queryUser1"/>
</groups>
<classes>
<classname="com.xiaofu.report.UserTest"/>
</classes>
</test>
</suite>
测试报告配置
手动配置一个测试报告侦听器类 ExtentTestNGIReporterListener,可以自行定义在测试报告上显示的数据,最后执行测试方法同时会生成测试报告。
/**
*@authorxiaofu
*@descriptionTestNg可视化配置
*@date2020/3/1916:44
*/
publicclassExtentTestNGIReporterListenerimplementsIReporter{
//生成的路径以及文件名
privatestaticfinalStringOUTPUT_FOLDER="target/test-report/";
privatestaticfinalStringFILE_NAME="index.html";
privateExtentReportsextent;
@Override
publicvoidgenerateReport(List<XmlSuite>xmlSuites,List<ISuite>suites,StringoutputDirectory){
init();
booleancreateSuiteNode=false;
if(suites.size()>1){
createSuiteNode=true;
}
for(ISuitesuite:suites){
Map<String,ISuiteResult>result=suite.getResults();
//如果suite里面没有任何用例,直接跳过,不在报告里生成
if(result.size()==0){
continue;
}
//统计suite下的成功、失败、跳过的总用例数
intsuiteFailSize=0;
intsuitePassSize=0;
intsuiteSkipSize=0;
ExtentTestsuiteTest=null;
//存在多个suite的情况下,在报告中将同一个一个suite的测试结果归为一类,创建一级节点。
if(createSuiteNode){
suiteTest=extent.createTest(suite.getName()).assignCategory(suite.getName());
}
booleancreateSuiteResultNode=false;
if(result.size()>1){
createSuiteResultNode=true;
}
for(ISuiteResultr:result.values()){
ExtentTestresultNode;
ITestContextcontext=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下的数据
intpassSize=r.getTestContext().getPassedTests().size();
intfailSize=r.getTestContext().getFailedTests().size();
intskipSize=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);
}
}
}
for(Strings:Reporter.getOutput()){
extent.setTestRunnerOutput(s);
}
extent.flush();
}
privatevoidinit(){
//文件夹不存在的话进行创建
FilereportDir=newFile(OUTPUT_FOLDER);
if(!reportDir.exists()&&!reportDir.isDirectory()){
reportDir.mkdirs();
}
ExtentHtmlReporterhtmlReporter=newExtentHtmlReporter(OUTPUT_FOLDER FILE_NAME);
//设置静态文件的DNS
//怎么样解决cdn.rawgit.com访问不了的情况
htmlReporter.config().setResourceCDN(ResourceCDN.EXTENTREPORTS);
htmlReporter.config().setDocumentTitle("用户服务自动化测试报告");
htmlReporter.config().setReportName("用户服务自动化测试报告");
htmlReporter.config().setChartVisibilityOnOpen(true);
htmlReporter.config().setTestViewChartLocation(ChartLocation.TOP);
htmlReporter.config().setTheme(Theme.STANDARD);
htmlReporter.config().setEncoding("utf-8");
htmlReporter.config().setCSS(".node.level-1ul{display:none;}.node.level-1.activeul{display:block;}");
extent=newExtentReports();
extent.attachReporter(htmlReporter);
extent.setReportUsesManualConfiguration(true);
}
privatevoidbuildTestNodes(ExtentTestextenttest,IResultMaptests,Statusstatus){
//存在父节点时,获取父节点的标签
String[]categories=newString[0];
if(extenttest!=null){
List<TestAttribute>categoryList=extenttest.getModel().getCategoryContext().getAll();
categories=newString[categoryList.size()];
for(intindex=0;index<categoryList.size();index ){
categories[index]=categoryList.get(index).getName();
}
}
ExtentTesttest;
if(tests.size()>0){
//调整用例排序,按时间排序
Set<ITestResult>treeSet=newTreeSet<ITestResult>(newComparator<ITestResult>(){
@Override
publicintcompare(ITestResulto1,ITestResulto2){
returno1.getStartMillis()<o2.getStartMillis()?-1:1;
}
});
treeSet.addAll(tests.getAllResults());
for(ITestResultresult:treeSet){
Object[]parameters=result.getParameters();
Stringname="";
//如果有参数,则使用参数的toString组合代替报告中的name
for(Objectparam:parameters){
name =param.toString();
}
if(name.length()==0){
name=result.getMethod().getMethodName();
}
if(extenttest==null){
test=extent.createTest(name);
}else{
//作为子节点进行创建时,设置同父节点的标签一致,便于报告检索。
test=extenttest.createNode(name).assignCategory(categories);
}
//test.getModel().setDescription(description.toString());
//test=extent.createTest(result.getMethod().getMethodName());
for(Stringgroup:result.getMethod().getGroups())
test.assignCategory(group);
List<String>outputList=Reporter.getOutput(result);
for(Stringoutput: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()));
}
}
}
privateDategetTime(longmillis){
Calendarcalendar=Calendar.getInstance();
calendar.setTimeInMillis(millis);
returncalendar.getTime();
}
}
会在指定的目录 target/test-report/ 下生成 index.html 测试报告文件,测试的成功率等信息显示的都比较直观,样式也还是蛮好看。
下面就简单介绍几个我常用的 testNG 测试场景
1、参数化测试使用 @DataProvider 注解为其他测试方法提供参数,queryUser 方法会执行 Object[][]数组中所有参数user1 、user2,相当于循环执行测试方法。
@DataProvider(name="paramDataProvider")
publicObject[][]paramDataProvider(){
Useruser1=newUser();
user1.setUserId(1);
user1.setUserName("程序员内点事1");
Useruser2=newUser();
user2.setUserId(2);
user2.setUserName("程序员内点事2");
returnnewObject[][]{{1,user1},{2,user2}};
}
@Test(dataProvider="paramDataProvider",groups="user")
publicvoidqueryUser(Integerindex,Useruser){
log.info("index:{},user:{}",index,JSON.toJSONString(user));
}
xml 方式下还可以在配置文件设置参数
<parametername="name"value="程序员内点事"/>
@Test(groups="user")
publicvoidqueryUser(Stringname){
log.info("我是测试方法~");
}
2、超时测试
可以给测试方法一个超时时间,如果实际执行时间超过设定的超时时间,用例将不通过。
@Test(timeOut=5000)
publicvoidtimeOutTest()throwsInterruptedException{
Thread.sleep(6000);
}
3、依赖测试
有时我们可能需要以特定顺序调用测试用例中的方法,或者希望在方法之间共享一些数据,TestNG支持在测试方法之间显式依赖的声明。
@Test
publicvoidtoken(){
System.out.println("gettoken");
}
@Test(dependsOnMethods={"token"})
publicvoidgetUser(){
System.out.println("thisistestgetUser");
}
总结
简单提了一下 TestNG 框架相关的知识,说实话本来就为给老铁弄个测试报告模板,一不留神说这么多。如果小伙伴们对这个测试框架感兴趣,下次我会出一份详细的 TestNG文章。
原创不易,燃烧秀发输出内容,如果有一丢丢收获,点个赞鼓励一下吧!
整理了几百本各类技术电子书相送 ,嘘~,「免费」 送给小伙伴们,私信或者评论【666】自行领取。和一些小伙伴们建了一个技术交流群,一起探讨技术、分享技术资料,旨在共同学习进步。