6.2.3 测试模块
单元测试会作为流水线的第一个任务被执行,用户可以选择当单元测试失败时是否中止后续的任务执行。执行单元测试之前,工程代码会被下载到测试服务器上的工作目录里,通过执行mvn test命令运行完成单元测试。
Java代码覆盖率的收集可以用Cobertura、Emma或者Jacoco。这三者之间的区别和优劣网上有很多文章已经给与了说明,这里就不多介绍了。我们选择Jacoco不仅是因为它还在持续更新中,而另外两个工具已经基本不再维护了,更重要的是它支持多种代码覆盖率收集的方式,不仅可以用传统的字节码注入的方式收集,更重要的是它提供的非入侵式的on-the-fly方式,非常适合用在我们的容器环境中来收集接口测试的代码覆盖率。
我们使用ant脚本驱动mvn执行单元测试和驱动Jacoco收集覆盖率,ant脚本如代码清单6-8所示。
代码清单6-8 收集代码覆盖率脚本
1.<?xml version="1.0" encoding="UTF-8"?> 2.<project name="${repository}" default="unittest" xmlns:jacoco="antlib:org.jacoco.ant"> 3. <description> 4. 单元测试运行并收集覆盖率 5. </description> 6. 7. <property name="jacoco.home" value="${jacocoHome}"/> 8. <property name="jacocoant.path" value="${jacoco.home}/lib/jacocoant.jar"/> 9. <property name="base.dir" location="${baseDir}"/> 10. <property name="code.dir" location="${base.dir}"/> 11. <property name="report.dir" location="${base.dir}/report"/> 12. <property name="exec.file" location="${base.dir}/jacoco.exec"/> 13. <property name="mvn.home" location="${mvnHome}"/> 14. <property name="mvn.mainclass" value="org.codehaus.plexus.classworlds.launcher.Launcher" /> 15. 16. <!-- 引入Jacoco --> 17. <taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml"> 18. <classpath path="${jacocoant.path}"/> 19. </taskdef> 20. 21. <!-- 清理工作空间 --> 22. <target name="mvn-clean"> 23. <path id="classpath"> 24. <fileset dir="${mvn.home}/boot"> 25. <include name="plexus-classworlds-*.jar" /> 26. </fileset> 27. </path> 28. 29. <!-- Ant脚本不直接支持Maven执行,通过Java命令行调用Maven --> 30. <java classname="${mvn.mainclass}" classpathref="classpath" dir="${code.dir}" fork="true" failonerror="true"> 31. <jvmarg value="-Dclassworlds.conf=${mvn.home}/bin/m2.conf" /> 32. <jvmarg value="-Dmaven.home=${mvn.home}" /> 33. <arg value="clean" /> 34. </java> 35. </target> 36. 37. <!-- 执行单元测试 --> 38. <target name="mvn-test" depends="mvn-clean"> 39. <path id="classpath"> 40. <fileset dir="${mvn.home}/boot"> 41. <include name="plexus-classworlds-*.jar" /> 42. </fileset> 43. </path> 44. 45. <!-- 在这个节点里面调用Java程序就可以收集到覆盖率 --> 46. <jacoco:coverage destfile="${exec.file}"> 47. <!-- Ant脚本不直接支持Maven执行,通过Java命令行调用Maven --> 48. <java classname="${mvn.mainclass}" classpathref="classpath" dir="${code.dir}" fork="true" failonerror="true"> 49. <jvmarg value="-Dclassworlds.conf=${mvn.home}/bin/m2.conf" /> 50. <jvmarg value="-Dmaven.home=${mvn.home}" /> 51. <jvmarg value="-DfailIfNoTests=false" /> 52. <arg value="test" /> 53. <arg value="-e" /> 54. <arg value="${mvnProfile}" /> 55. </java> 56. </jacoco:coverage> 57. </target> 58. 59. <!-- 收集覆盖率报告 --> 60. <target name="report" depends="mvn-test"> 61. <delete dir="${report.dir}" /> 62. <mkdir dir="${report.dir}" /> 63. 64. <jacoco:report> 65. <executiondata> 66. <file file="${exec.file}"/> 67. </executiondata> 68. 69. <!-- 指定对应的代码的编译出的字节码的地址 --> 70. <structure name="JaCoCo Report"> 71. <group name="${module.group}"> 72. <classfiles> 73. <fileset dir="${code.dir}/${module.name}/target/classes/"/> 74. </classfiles> 75. <sourcefiles encoding="module.encoding"> 76. <fileset dir="${code.dir}/${module.name}/src/main/java/"/> 77. </sourcefiles> 78. </group> 79. </structure> 80. 81. <!-- 产生不同格式的报告 --> 82. <html destdir="${report.dir}" encoding="utf-8"/> 83. <!-- <csv destfile="${report.dir}/report.csv"/> --> 84. <!-- <xml destfile="${report.dir}/report.xml"/> --> 85. </jacoco:report> 86. </target> 87. 88. <!-- 按顺序执行脚本定义的任务 --> 89. <target name="unittest" depends="mvn-clean,mvn-test,report"/> 90.</project> |
需要注意的是<structure>/<group>节点,针对多模块的工程,需要为每个收集代码覆盖率的模块定义<group>。在程序中,为了使脚本具有更强的通用性,可以使用Thymeleaf等模板处理工具来替换脚本中的信息或者扩展脚本,这里就不做过多说明了。然后用ProcessBuilder执行这个ant脚本即可,如代码清单6-9所示。
代码清单6-9 执行代码覆盖率脚本
1.String osName = System.getProperty("os.name").toLowerCase(); 2.// 根据操作系统选择合适的ant路径 3.String cmd = ""; 4.if (osName.indexOf("linux") >= 0) { 5. cmd = antHome + "/bin/ant.sh"; 6.} else if (osName.indexOf("windows") >= 0) { 7. cmd = antHome + "/bin/ant.bat"; 8.} 9.try { 10. // 通过命令行执行ant脚本 11. ProcessBuilder pb = new ProcessBuilder(cmd); 12. // 重定向日志 13. pb.directory(workspace.toFile()) 14. .redirectOutput(new File(workspace.toString() + "/output.log")) 15. .redirectError(new File(workspace.toString() + "/error.log")); 16. Process process = pb.start(); 17. // 验证执行是否成功 18. int exitVal = process.waitFor(); 19. // exitVal不为0则执行失败 20. if (exitVal != 0) { 21. logger.error("Coverage data collecting process not finished with expected value, exit value is {}", exitVal); 22. }else { 23. logger.debug("Unit tests passed and coverage data collecting process finished successfully"); 24. } 25.} catch (IOException e) { 26. logger.error("Coverage data collecting process was failed with IOException thrown", e); 27.} catch (InterruptedException e) { 28. logger.error("Coverage data collecting process was failed with InterruptedException thrown", e); 29.} |
接口测试的代码覆盖率收集和单元测试的有些不同。接口测试需要在测试环境部署之后针对新环境执行。接口测试的实现可能有不同的方式,不过共同点是一般情况测试代码和应用系统代码不在同一个工程里。我们做接口测试的方式已经在第五章详细介绍过了,在持续集成的过程中我们通过接口测试平台提供的RESTful API。这种情况下对于应用来说,收集接口测试代码覆盖率就可以使用Jacoco的on-the-fly模式远程收集。这种模式不需要对应用程序的字节码进行注入,也不需要对应用做任何修改。以tomcat做中间件为例,只需要通过以下方法进行收集:
1. 在JVM的启动参数里加上xxxx,然后启动应用
2. 准备ant脚本,和脚本6-8的区别就是,去掉了<target name="mvn-clean">和<target name="mvn-test">,在收集报告之前添加了以下节点,如代码清单6-10所示。
代码清单6-10 执行代码覆盖率脚本
1.<target name="dump">
2. <jacoco:dump address="${server.ip}" reset="false" destfile="${base.dir}/jacoco.exec" port="${server.port}" append="true"/>
3.</target>
server.ip就是步骤1中的应用服务器的ip地址,server.port就是上面打开的端口。
通过以上的方式,我们就实现了持续集成流水线的执行单元测试和接口测试并收集到了代码覆盖率。
相关推荐:
版权声明:51Testing软件测试网获人民邮电出版社和作者授权连载本书部分章节。
任何个人或单位未获得明确的书面许可,不得对本文内容复制、转载或进行镜像,否则将追究法律责任。