3 分钟生成一个单元测试报告,这个样式爱了

昨天有个小伙伴问我 , 有没有什么现成的测试报告模板 , 由于昨天实在比较忙就没顾上 , 所以今个有时间赶紧补上 。 一般力所能及的事 , 只要我有时间都会为大家解决 , 但毕竟能力有限做不到的地方小伙伴们也多理解 。
平时我们开发接口时 , Junit单元测试是最为常用的一种开发测试手段 , 很多时候测试其实只看接口是否正常返回结果就ok了 。 但有时间我们要测试一些特殊场景 , 如:接口超时测试等 , 就没什么太好的办法了 , 而TestNG实现容易的多 。 它与JUnit用法十分相似 , 只要你用过JUnit分分钟上手 。
大致讲一下TestNG的几个重要概念 , @Test注解标注的方法是最小的执行单元 , 我们可以将这些单个的测试用例划分成group分组管理 , group可以用在测试类或者方法上 , suite套件可以理解成测试类的容器 。
核心依赖引入extentreports和testng
org.springframework.bootspring-boot-starter-testtestorg.junit.vintagejunit-vintage-engineorg.testngtestng7.1.0testcom.aventstackextentreports3.0.6TestNG配置TestNG支持两种执行方式 , 第一种是用注解像Junit直接点方法名run执行 。 第二种配置xml文件的方式 。
@Slf4j@Listeners({ExtentTestNGIReporterListener.class})@SpringBootTest(classes=SpringbootTestngReportApplication.class)publicclassUserTestextendsAbstractTestNGSpringContextTests{@DataclassUser{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就运行了 。
suiteSYSTEM"">测试报告配置手动配置一个测试报告侦听器类ExtentTestNGIReporterListener , 可以自行定义在测试报告上显示的数据 , 最后执行测试方法同时会生成测试报告 。
/***@authorxiaofu*@descriptionTestNg可视化配置*@date2020/3/1916:44*/publicclassExtentTestNGIReporterListenerimplementsIReporter{//生成的路径以及文件名privatestaticfinalStringOUTPUT_FOLDER="target/test-report/";privatestaticfinalStringFILE_NAME="index.html";privateExtentReportsextent;@OverridepublicvoidgenerateReport(ListxmlSuites,Listsuites,StringoutputDirectory){init();booleancreateSuiteNode=false;if(suites.size()>1){createSuiteNode=true;}for(ISuitesuite:suites){Mapresult=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){ListcategoryList=extenttest.getModel().getCategoryContext().getAll();categories=newString[categoryList.size()];for(intindex=0;indexsize()>0){//调整用例排序 , 按时间排序SettreeSet=newTreeSet(newComparator(){@Overridepublicintcompare(ITestResulto1,ITestResulto2){returno1.getStartMillis()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);ListoutputList=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测试报告文件 , 测试的成功率等信息显示的都比较直观 , 样式也还是蛮好看 。
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方式下还可以在配置文件设置参数
@Test(groups="user")publicvoidqueryUser(Stringname){log.info("我是测试方法~");}2、超时测试可以给测试方法一个超时时间 , 如果实际执行时间超过设定的超时时间 , 用例将不通过 。
@Test(timeOut=5000)publicvoidtimeOutTest()throwsInterruptedException{Thread.sleep(6000);}3、依赖测试【3 分钟生成一个单元测试报告,这个样式爱了】有时我们可能需要以特定顺序调用测试用例中的方法 , 或者希望在方法之间共享一些数据 , TestNG支持在测试方法之间显式依赖的声明 。
@Testpublicvoidtoken(){System.out.println("gettoken");}@Test(dependsOnMethods={"token


    推荐阅读