昨天有个小伙伴问我,有没有什么现成的测试报告模板,因为昨天切实比较忙就没顾上,所以今个有工夫连忙补上。个别力不从心的事,只有我有工夫都会为大家解决,但毕竟能力无限做不到的中央小伙伴们也多了解。
平时咱们开发接口时,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)public class UserTest extends AbstractTestNGSpringContextTests { @Data class User { private Integer userId; private String userName; } /** * 参数提供 */ @DataProvider(name = "paramDataProvider") public Object[][] paramDataProvider() { User user1 = new User(); user1.setUserId(1); user1.setUserName("程序员内点事1"); User user2 = new User(); user2.setUserId(2); user2.setUserName("程序员内点事2"); return new Object[][]{{1, user1}, {2, user2}}; } @Test(dataProvider = "paramDataProvider") public void queryUser(Integer index, User user) { if (index == 2) { int a = 1 / 0; } log.info("index:{},user: {}", index, JSON.toJSONString(user)); Assert.assertTrue(Objects.nonNull(user)); }}
xml
形式间接右键 .xml
文件 run
就运行了。
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"><suite name="用户单元测试" parallel="classes" thread-count="5"> <listeners> <listener class-name="com.xiaofu.report.config.ExtentTestNGIReporterListener"/> </listeners> <test verbose="1" name="用户测试"> <parameter name="userId" value="1"/> <parameter name="userName" value="程序员内点事"/> <groups> <define name="queryUser"/> <define name="queryUser1"/> </groups> <classes> <class name="com.xiaofu.report.UserTest"/> </classes> </test></suite>
测试报告配置
手动配置一个测试报告侦听器类 ExtentTestNGIReporterListener
,能够自行定义在测试报告上显示的数据,最初执行测试方法同时会生成测试报告。
/** * @author xiaofu * @description TestNg 可视化配置 * @date 2020/3/19 16:44 */public class ExtentTestNGIReporterListener implements IReporter { //生成的门路以及文件名 private static final String OUTPUT_FOLDER = "target/test-report/"; private static final String FILE_NAME = "index.html"; private ExtentReports extent; @Override public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) { init(); 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); } } } for (String s : Reporter.getOutput()) { extent.setTestRunnerOutput(s); } extent.flush(); } private void init() { //文件夹不存在的话进行创立 File reportDir = new File(OUTPUT_FOLDER); if (!reportDir.exists() && !reportDir.isDirectory()) { reportDir.mkdirs(); } ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(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-1 ul{ display:none;} .node.level-1.active ul{display:block;}"); extent = new ExtentReports(); extent.attachReporter(htmlReporter); extent.setReportUsesManualConfiguration(true); } 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) { 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 (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(); }}
会在指定的目录 target/test-report/
下生成 index.html
测试报告文件,测试的成功率等信息显示的都比拟直观,款式也还是蛮难看。
测试场景
下边就简略介绍几个我罕用的 testNG 测试场景
1、参数化测试
应用 @DataProvider
注解为其余测试方法提供参数,queryUser
办法会执行 Object[][]
数组中所有参数user1 、user2,相当于循环执行测试方法。
@DataProvider(name = "paramDataProvider")public Object[][] paramDataProvider() { User user1 = new User(); user1.setUserId(1); user1.setUserName("程序员内点事1"); User user2 = new User(); user2.setUserId(2); user2.setUserName("程序员内点事2"); return new Object[][]{{1, user1}, {2, user2}};}@Test(dataProvider = "paramDataProvider",groups = "user")public void queryUser(Integer index, User user) { log.info("index:{},user: {}", index, JSON.toJSONString(user));}
xml 形式下还能够在配置文件设置参数
<parameter name="name" value="程序员内点事"/>
@Test(groups = "user")public void queryUser(String name) { log.info("我是测试方法~");}
2、超时测试
能够给测试方法一个超时工夫,如果理论执行工夫超过设定的超时工夫,用例将不通过。
@Test(timeOut = 5000)public void timeOutTest() throws InterruptedException { Thread.sleep(6000);}
3、依赖测试
有时咱们可能须要以特定顺序调用测试用例中的办法,或者心愿在办法之间共享一些数据,TestNG
反对在测试方法之间显式依赖的申明。
@Test public void token() { System.out.println("get token"); } @Test(dependsOnMethods= {"token"}) public void getUser() { System.out.println("this is test getUser"); }
总结
简略提了一下 TestNG
框架相干的常识,说实话原本就为给老铁弄个测试报告模板,一不留神说这么多。如果小伙伴们对这个测试框架感兴趣,下次我会出一份具体的 TestNG
文章。
原创不易,焚烧秀发输入内容,如果有一丢丢播种,点个赞激励一下吧!
习惯在VX看技术文章,想要获取更多Java资源的同学,能够关注我的公众号:程序员内点事,暗号:[666]