自动化测试--实现一套完全解耦的简单测试框架(二)
一:每次运行都需要打开代码工具,如eclipse或者IDE等。为了后面的持续集成,直接使用Maven命令去运行自动化测试,需要引入surfire插件。笔者使用的是2.10版本Surefire和6.9.10版本TESTNG。
下面是项目完整的pom文件:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.claire.leafly</groupId> <artifactId>projectLemon</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>3.4.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.testng/testng --> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>6.9.10</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/dom4j/dom4j --> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.17</version> </dependency> <dependency> <groupId>xml-apis</groupId> <artifactId>xml-apis</artifactId> <version>1.4.01</version> </dependency> <!-- https://mvnrepository.com/artifact/org.uncommons/reportng --> <dependency> <groupId>org.uncommons</groupId> <artifactId>reportng</artifactId> <version>1.1.4</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/com.google.inject/guice --> <dependency> <groupId>com.google.inject</groupId> <artifactId>guice</artifactId> <version>3.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.1</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency> </dependencies> <build> <plugins> <!-- 1:解决每次右键项目名-maven->update project 时候,项目jdk版本变了,变回1.5版本或者其他版本 2: 解决使用maven编译其他问题:如提示不能在内部类访问外部非final局部变量 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.10</version> <configuration> <systemPropertyVariables> <org.uncommons.reportng.escape-output>false</org.uncommons.reportng.escape-output>//是否忽略html,解释见下图。与之后在reportNg报告上显示截图相关。 </systemPropertyVariables> <testFailureIgnore>true</testFailureIgnore>//测试失败后,是否忽略并继续测试 <argLine> -Dfile.encoding=UTF-8 </argLine> <suiteXmlFiles> <suiteXmlFile>register.xml</suiteXmlFile>//代表的是要执行的测试套件名称 </suiteXmlFiles> </configuration> </plugin> </plugins> </build> </project>
该配置与reportng相关,后面有详细的解释和源码。
在pom中设置好surefire插件之后,即可使用maven test来执行设置好的测试套件。
注意点(坑):
1.读者使用的各插件或者jar包版本与笔者不同,在执行maventest时,如果未能执行测试套件。请尝试选择不同的suifire插件版本。
2.如上配置的<suiteXmlFile>register.xml</suiteXmlFile>---------------------register.xml该文件一般都放置在根目录下,否则有时会报错
二、在测试完成之后,通常希望得到一份完善的测试报告。TESTNG的测试报告,太过简陋,笔者引入reportNG的jar包。
1.引入reportng依赖
2.在register.xml中,添加监听器NewHtmlReporter,用于渲染reportng 生成的测试报告。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" > <suite name="suitName_register" parallel="false"> <test name="testName_register"> <classes> <!-- <class name="com.demo.auto.claire.testcase.register.Register_FailTester_001" /> --> <class name="com.demo.auto.claire.testcase.register.Register_SuccessTester_002" /> </classes> </test> <!-- Test --> <listeners> <!-- 添加监听,生成测试报告 --> <listener class-name="org.uncommons.reportng.HTMLReporter" />//先添加该监听器,后面添加自定义监听器NewHtmlReporter后,该监听器就可以删掉了
<!-- 添加自定义监听,该监听继承了reportNg的 HTMLReporter-->
<!-- <listener class-name="com.demo.auto.claire.util.NewHtmlReporter" />-->
<!--添加自定义监听器,在测试失败的时候进行截图 -->
<listener class-name="com.demo.auto.claire.listener.ListenerFailTestScreenShot">
</listener>
</listeners>
</suite>
<!-- Suite -->
注意(坑):
对项目右键--run as --maven test时,如果报Injector类找不到时,需要引入guice依赖。(也可以升级testng的版本,查找到依赖了guice jar的相应版本即可)
三:此时使用reportng生成了测试报告,比testng的要美观一些。但是我们希望可以对其进行定制。
1.显示出每一列的名称
2.显示错误的截图.效果如下:
1.在测试完成之后,需要对失败的用例进行截图。自定义监听器,在测试失败的时候,进行截图。
看一下关于监听的继承和实现关系图
可以看到,ITestListener接口中定义了方法onTestFailure,自定义监听类可以实现改接口,重写该方法。但是,此时需要重写改接口所有的方法。即使方法体里面为空,代码也不会美观。最后笔者选择将自定义监听器继承类TestListenerAdapter,只重写该类中的onTestFailure方法即可。下面是代码示例:
1 package com.demo.auto.claire.listener; 2 3 import java.io.File; 4 import java.io.IOException; 5 import java.text.SimpleDateFormat; 6 import java.util.Calendar; 7 8 import org.apache.bcel.generic.SIPUSH; 9 import org.testng.ITestResult; 10 import org.testng.Reporter; 11 import org.testng.TestListenerAdapter; 12 13 import com.demo.auto.claire.base.BaseTester; 14 import com.demo.auto.claire.util.ScreenShotUtil; 15 16 public class ListenerFailTestScreenShot extends TestListenerAdapter { 17 18 @Override 19 public void onTestFailure(ITestResult tr) { 20 21 // 获取到当前的年月日 22 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); 23 Calendar calendar = Calendar.getInstance(); 24 //calendar.getTime()得到的时间是这样的:Fri Aug 31 16:16:51 CST 2018,需要格式化成年月日 25 String time = dateFormat.format(calendar.getTime()); 26 //也可以直接使用 27 //String time2 = dateFormat.format(new java.util.Date() 28 29 //获得几点周几 30 //SimpleDateFormat dateFormat2 = new SimpleDateFormat("EEEE"); 31 //String week = dateFormat2.format(new java.util.Date()); 32 33 // D:\myTest\projectLemon\target\surefire-reports\registertest_register_success_002\2018-08-31 34 35 String outPutDirectory=tr.getTestContext().getOutputDirectory(); 36 //获取到测试的方法名称 37 String methodName= tr.getName(); 38 39 String path1 = outPutDirectory +File.separator+methodName +File.separator+time; 40 /*System.out.println("##################################"); 41 System.out.println(path1); 42 System.out.println(outPutDirectory); 43 System.out.println(methodName);*/ 44 45 String imgName=null; 46 47 48 try { 49 //当测试不通过的时候,调用截图的代码 50 imgName = ScreenShotUtil.screenShot(BaseTester.driver, path1); 51 } catch (IOException e) { 52 53 e.printStackTrace(); 54 } 55 56 //Reporter.log("测试代码"); 57 //http://localhost:7777/registertest_register_success_002/2018-09-01%E6%98%9F%E6%9C%9F%E5%85%AD/1535807491038.png 58 //String imgHerf2 ="/" +methodName+"/"+ time+"/"+imgName; 59 String suitName = tr.getTestContext().getCurrentXmlTest().getSuite().getName(); 60 String imgHerf = "../" +String.join("/",suitName, methodName,time,imgName); 61 //下面这句代码是将截图添加到reportNg的报告中使用的,log的内容会显示在报告上 62 Reporter.log("<img src='" + imgHerf +"' width='100' height='100'><a href='"+imgHerf+ "' target='_blank'>查看大图</a></img>"); 63 // System.out.println("******************************"); 64 // System.out.println(imgHerf); 65 66 67 super.onTestFailure(tr); 68 } 69 70 }
该监听器监听到测试失败之后,会调用截图帮助类中的方法进行截图。代码示例:
package com.demo.auto.claire.util; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import org.apache.commons.io.FileUtils; import org.openqa.selenium.OutputType; import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; import org.testng.ITestResult; public class ScreenShotUtil { public static void main(String[] args) { WebDriver driver = SeleniumUtil.getDriver(); //driver.get("http://www.baidu.com"); // screenShot(driver); } public static String screenShot(WebDriver driver,String path) throws IOException { File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); //String path = sr.getTestContext().getOutputDirectory(); //FileUtils.copyFileToDirectory(srcFile, new File(path)); //冒号是路径非法字符,路径中不能出现冒号,所以时分秒选择了使用“-” String fileName = (new SimpleDateFormat("HHmmss")).format(new Date())+".png"; String pathname = path+File.separator+ fileName ; System.out.println("pathname:"+pathname); FileUtils.copyFile(srcFile, new File(pathname)); return fileName; } }
如果是使用maventest执行的测试,则截图存储在target目录下面.(右键test-output,找到同级的target目录-----------surfire-reports ---------------- 套件名称----------- 测试名称 --------------年月日 --------时分秒.png)
如果使用的是testng执行的测试,则截图保存在test-output--------------------surfire-reports ---------------- 套件名称----------- 测试名称 --------------年月日 --------时分秒.png
2.下面讲一下,如何定制reportng
a.修改其配置文件,增加列名
上图中的文本是:(可根据喜好,自行配置)-----修改完成之后,记得托回到jar包中去
log=Log Info
screenshot=Screen Shot
duration=Duration
打开并修改上述文件:
上图中的文本是:-----修改完成之后,记得托回到jar包中去
#if ($failedTests.size() > 0) <table class="resultsTable"> <tr><th colspan="4" class="header failed">$messages.getString("failedTests")</th></tr> #foreach ($testClass in $failedTests.keySet()) <tr> <td colspan="1" class="group">$testClass.name</td> <td colspan="1" class="group">$messages.getString("duration")</td> <td colspan="1" class="group">$messages.getString("log")</td> <td colspan="1" class="group">$messages.getString("screenshot")</td> </tr> #set ($classResults = $failedTests.get($testClass)) #parse ("org/uncommons/reportng/templates/html/class-results.html.vm") #end </table> #end
上面两步,完成了给测试失败的用例,添加3个列名。
接下来修改源码和模板文件,将截图添加进入。
在上述的测试失败进行截图的监听器中,有一句代码是这样的
reporter.log("XXX")-------这个xxx的内容就会显示在我们的Log info列。---------------下一步,就是要将图片从log info列删除,在screen shot列添加:
打开该指定文件。
上图文本为:
## Show logger output for the test. #set ($output = $utils.getTestOutput($testResult)) #if ($output.size() > 0) <div class="testOutput"> #foreach( $line in $output ) #if ($meta.shouldEscapeOutput()) $utils.escapeHTMLString($tools.removeImage($line))<br /> #else $tools.removeImage($line)<br/> #end #end </div> #end
上图文本为:
<td class="screenshot"> ## Show logger output for the test. #set ($output = $utils.getTestOutput($testResult)) #if ($output.size() > 0) <div class="screenshotimage"> #foreach( $line in $output ) #if ($meta.shouldEscapeOutput()) $utils.escapeHTMLString($tools.getImageString($line))<br /> #else $tools.getImageString($line)<br/> #end #end </div> #end </td>
可以看到,上面试使用tools.getImageString() 、tools.removeImage来获取和删除图片的。这个tools是哪里来的呢?
我们看到reportng自己的方法都是使用的 这个util的对象是从哪里注册进来的?
可以看到HTMLreporter继承了AbstractReporter,AbstractReporter中定义了一个final的常量UTILS----引用指向的是ReportNGUtils对象,该ReportNGUtils定义了各种方法,供模板文件中调用。又通过creatContext的方式注册进模板文件(VelocityContext)对象中.
那么想要在模板文件中删除和加上图片,有2种方式,
第一:修改ReportNGUtils类,在其中加上删除和引入图片的方法。会涉及到修改源码,修改源码通常也有2种方式:
1.下载源码,修改,完成后重新打jar包并引入------------操作起来比较麻烦
2.在自己的项目下,生成一个与reportng一致的包名和类名(org.uncommons.reportng.HTMLReporter.class),在其中修改代码。----------这样的话,如果别人引入了你的jar包又引入了reportng的jar包,要考虑下到底使用的谁的。
第二:自定义一个NewHtmlReporter,去继承HTMLReporter,重写父类的creatContext方法,将自己的工具类引入。
下面是NewHtmlReporter 的源码
package com.demo.auto.claire.util; import org.apache.bcel.generic.NEW; import org.apache.velocity.VelocityContext; import org.uncommons.reportng.HTMLReporter; public class NewHtmlReporter extends HTMLReporter{ private static final ReportNgTools TOOLS = new ReportNgTools(); @Override protected VelocityContext createContext() { VelocityContext context = super.createContext(); context.put("tools", TOOLS); return context; } }
下面是自己实现了删除和增加截图的工具类ReportNgTools源码:
package com.demo.auto.claire.util; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ReportNgTools { public String sayHello() { return "love"; } public String removeImage(String log) { //正则表达式 String regex = "<img .*>.*</img>"; //匹配到正则的内容,将其用空字符替换 return log.replaceAll(regex, ""); } public String getImageString(String log) { //正则表达式 String regex = "<img .*>.*</img>"; //将正则表达式编译为Pattern对象 Pattern pattern = Pattern.compile(regex); //通过正则表达式对象去匹配log内容 Matcher matcher = pattern.matcher(log); //如果匹配到了,则返回第0组(第0组是包含了所有匹配到的数据) if (matcher.find()) { return matcher.group(0); } //否则返回空 return ""; } }
此时,我们直接引入自己的监听即可:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" > <suite name="suitName_register" parallel="false"> <test name="testName_register"> <classes> <!-- <class name="com.demo.auto.claire.testcase.register.Register_FailTester_001" /> --> <class name="com.demo.auto.claire.testcase.register.Register_SuccessTester_002" /> </classes> </test> <!-- Test --> <listeners> <!-- 添加监听,生成测试报告。这个就可以删除了 --> <!-- <listener class-name="org.uncommons.reportng.HTMLReporter" /> --> <!-- 添加自定义监听,该监听继承了reportNg的 HTMLReporter--> <listener class-name="com.demo.auto.claire.util.NewHtmlReporter" /> <!--添加自定义监听器,在测试失败的时候进行截图 --> <listener class-name="com.demo.auto.claire.listener.ListenerFailTestScreenShot"></listener> </listeners> </suite> <!-- Suite -->
综上就完成了关于reportng定制的部分