说到接口我们都能想到 jmeter 这个工具,但是我们如何借助 jenkins 来做可持续集成自动化的测试呢?下面就是我对 jenkins、maven、jmeter 做的可持续化集成环境搭建的学习心得。

一,jmeter 脚本录制

这一步就是录制 Jmeter 脚本,确保脚本能正确运行。

二,建立一个 maven 工程

1,打开 eclipse,点击 File->New->Project->Maven Project

2,创建 maven 工程后,在工程目录下新建所需的文件夹

--Src/test/resources --存放报告模板文件
--src/main/

--src/test/jmeter --存放 jmeter 脚本文件跟一些接口调用的参数
--target/jmeter/bin --target 存放结果的根目录
--target/jmeter/html
--target/jmeter/html1
--target/jmeter/lib
--target/jmeter/logs
--target/jmeter/results
--target/maven-archiver
--target/surefire

3,编辑 pom.xml 文件

<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>testDemo</groupId>
    <artifactId>bluemoon</artifactId>
    <version>1.11</version>
    <name>bluemoon Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>3.0-alpha-1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.13</version>
        </dependency>
        <!-- <dependency> <groupId>org.apache.jmeter</groupId> <artifactId>ApacheJMeter_jdbc</artifactId> 
            <version>2.11</version> </dependency> <dependency> <groupId>com.oracle</groupId> 
            <artifactId>ojdbc14</artifactId> <version>10.2.0.5</version> </dependency> -->
        <!-- MySql 5.5 Connector -->
    </dependencies>
    <properties>
        <jmeter.result.jtl.dir>${project.build.directory}\jmeter\results</jmeter.result.jtl.dir>
        <jmeter.result.html.dir>${project.build.directory}\jmeter\html</jmeter.result.html.dir>
        <jmeter.result.html.dir1>${project.build.directory}\jmeter\html1</jmeter.result.html.dir1>
        <ReportName>TestReport</ReportName>
    </properties>
    <pluginRepositories>
        <pluginRepository>
            <id>Codehaus repository</id>
            <url>http://repository.codehaus.org/</url>
            <releases>
                <enabled>true</enabled>
                <updatePolicy>always</updatePolicy>
            </releases>
            <snapshots>
                <enabled>true</enabled>
                <updatePolicy>always</updatePolicy>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
    <build>
        <finalName>bluemoon</finalName>
        <plugins>
            <plugin>
                <groupId>com.lazerycode.jmeter</groupId>
                <artifactId>jmeter-maven-plugin</artifactId>
                <version>1.10.0</version>
                <configuration>
                    <resultsFileFormat>xml</resultsFileFormat>
                    <ignoreResultFailures>true</ignoreResultFailures>
                    <testResultsTimestamp>flase</testResultsTimestamp>
                </configuration>
                <executions>
                    <execution>
                        <id>test</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>jmeter</goal>
                        </goals>
                        <!-- <configuration> <resultsFileFormat>csv</resultsFileFormat> </configuration> -->
                    </execution>
                </executions>
            </plugin>
            <!-- <plugin> <groupId>org.apache.jmeter</groupId> <artifactId>ApacheJMeter_jdbc</artifactId> 
                <version>2.11</version> </plugin> -->

            <plugin> 
              <artifactId>maven-compiler-plugin</artifactId> 
              <configuration> 
                  <source>1.6</source> 
                  <target>1.6</target> 
                  <encoding>UTF-8</encoding> 
                  <compilerArguments> 
                   <extdirs>src\test\jmeter\lib</extdirs> 
                 </compilerArguments> 
              </configuration> 
            </plugin> 

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>xml-maven-plugin</artifactId>
                <version>1.0-beta-3</version>
                <executions>
                    <execution>
                        <phase>verify</phase>
                        <goals>
                            <goal>transform</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <transformationSets>
                        <transformationSet>
                            <dir>${jmeter.result.jtl.dir}</dir>
                            <stylesheet>src\test\resources\jmeter-results-detail-report_21.xsl</stylesheet>
                            <outputDir>${jmeter.result.html.dir}</outputDir>
                            <fileMappers>
                                <fileMapper
                                    implementation="org.codehaus.plexus.components.io.filemappers.FileExtensionMapper">
                                    <targetExtension>html</targetExtension>
                                </fileMapper>
                            </fileMappers>
                        </transformationSet>
                        <transformationSet>
                            <dir>${jmeter.result.jtl.dir}</dir>
                            <stylesheet>src\test\resources\jmeter.results.shanhe.me.xsl</stylesheet>
                            <outputDir>${jmeter.result.html.dir1}</outputDir>
                            <fileMappers>
                                <fileMapper
                                    implementation="org.codehaus.plexus.components.io.filemappers.FileExtensionMapper">
                                    <targetExtension>html</targetExtension>
                                </fileMapper>
                            </fileMappers>
                        </transformationSet>
                    </transformationSets>

                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/jmeter/lib</outputDirectory>
                            <overWriteReleases>false</overWriteReleases>
                            <overWriteSnapshots>false</overWriteSnapshots>
                            <overWriteIfNewer>true</overWriteIfNewer>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>theMainClass</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
        <defaultGoal>clean</defaultGoal>
    </build>
</project>

4,调试工程,确保工程能正常运行

三,安装 jenkins 跟一些必要的插件
1,下载对应 windows 安装的 jenkins

2,安装好 jenkins 后本地启动 jenkins,进入插件管理界面,国内由于被墙了,所以需要配置一下。系统管理->管理插件->

高级->升级站点 中输入:http://updates.jenkins-ci.org/update-center.json 提交保存后,在地址栏输入

http://localhost:8080/restart重启,再次进入插件管理界面这时可选插件界面就会有内容了

3,需要安装的插件

Maven Integration plugin Maven 插件

Performance plugin 性能报告插件

HTML Publisher plugin HTTP report 插件

Git plugin GIT 插件

4,插件安装好后,就可以新建一个项目了,这里我们选择构建一个 maven 项目,名称为 bluemoon

选择 Git,输入 Git 服务器的 URL 地址,用户名跟密码

Build 选项中输入 pom.xml 文件路径

添加构建后操作,增加一个 Publish HTML reports,html directory to archive 为转译后 html 文件的保存路径,

index pages 为转译后的文件名称,report title 为 jenkins 中显示的标题

构建后操作还可以添加邮件服务模块,就是构建成功后会发送邮件通知,这里就不举例了。

5,点击应用,保存。这时就可以点击立即构建来触发构建功能了

6,Console putput 可以查看输入日志,方便构建时查看哪里出现的问题

7,构建成功后,我们就可以查看脚本运行后的结果,点击 performace 或 function 来查看

这里可以看到生成的 html 在 jenkins 使用 html publisher 查看报告时,发现显示不美观,不全的现象,很多东西显示不了。

在查看官方文档后,这原来是安全问题所导致的。

Jenkins 安全默认是将以下功能都关闭了

1、javascript
2、html 上的内置插件
3、内置 css 或从其它站的 css
4、从其它站的图处
5、AJAX

我的网页使用的是 css,所以显示不全。

解决这个问题可以在 jenkins 系统管理中输入以下脚本运行,就可以解决这个问题了

System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "")

四,最终效果

五,遇到的其他一些问题
1、当我们 jmeter 脚本中要运用 jdbc 来连接数据库时,此时是需要 jdbc 驱动的,这里解决的办法就是在 dependecy 中增

加依赖,然后再把这个依赖包拷贝到 target 的 lib 库中,这样用于引用 jdbc 的 jar 包就放到了 lib 中,也就解决了找不到 jdbc
驱动的问题

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.13</version>
   </dependency>
<plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <executions>
                <execution>
                    <id>copy-dependencies</id>
                    <phase>prepare-package</phase>
                    <goals>
                        <goal>copy-dependencies</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>${project.build.directory}/jmeter/lib</outputDirectory>
                        <overWriteReleases>false</overWriteReleases>
                        <overWriteSnapshots>false</overWriteSnapshots>
                        <overWriteIfNewer>true</overWriteIfNewer>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                        <classpathPrefix>lib/</classpathPrefix>
                        <mainClass>theMainClass</mainClass>
                    </manifest>
                </archive>
            </configuration>
        </plugin>

2、如下图中,报告中有写图标是没有显示出来的,造成这个问题的原因是我们用 jenkins 持续集成时把把代码拷到 target 工程中没有把相应的图标拷贝过去,那么我们只
要把这个图标拷贝过去不就可以解决了吗?答案是肯定的。

把这两个图标文件一起放到工程中的 src/test/resources 目录下,再在 pox 中添加以下插件即可

这里我在 pom 文件中添加了一个插件,代码如下:

                <plugin>
    <artifactId>maven-resources-plugin</artifactId>
    <executions>
        <execution>
            <id>copy-resources</id>
            <phase>compile</phase>
            <goals>
                <goal>copy-resources</goal>
            </goals>
            <configuration>
                <!-- test可以在环境变量中定义也可以将下面写成绝对地址 -->
                <outputDirectory>${project.build.directory}/jmeter/html</outputDirectory>
                <resources>
                    <resource>
                        <!-- basedir标识所有工程 -->
                        <directory>${basedir}/src/test/resources</directory>
                        <filtering>true</filtering>
                    </resource>
                </resources>
            </configuration>
        </execution>
    </executions>
</plugin>

3、这里还需要注意的一个地方就是,在 src/test/jmeter 目录下要添加一个 jmeter.proprtties 文件,这个文件可以从我们自己下载的 jmeter 解压包中 bin 中找到,然后修改文件中对应内容为如下:


jmeter.save.saveservice.data_type=true
jmeter.save.saveservice.label=true
jmeter.save.saveservice.response_code=true
# response_data is not currently supported for CSV output
jmeter.save.saveservice.response_data=true
# Save ResponseData for failed samples
jmeter.save.saveservice.response_data.on_error=false
jmeter.save.saveservice.response_message=true
jmeter.save.saveservice.successful=true
jmeter.save.saveservice.thread_name=true
jmeter.save.saveservice.time=true
jmeter.save.saveservice.subresults=true
jmeter.save.saveservice.assertions=true
jmeter.save.saveservice.latency=true
jmeter.save.saveservice.connect_time=true
jmeter.save.saveservice.samplerData=true
jmeter.save.saveservice.responseHeaders=true
jmeter.save.saveservice.requestHeaders=true
jmeter.save.saveservice.encoding=false
jmeter.save.saveservice.bytes=true
# Only available with HttpClient4
jmeter.save.saveservice.sent_bytes=true
jmeter.save.saveservice.url=true
jmeter.save.saveservice.filename=true
jmeter.save.saveservice.hostname=true
jmeter.save.saveservice.thread_counts=true
jmeter.save.saveservice.sample_count=true
jmeter.save.saveservice.idle_time=true

这里这样做的目的是在我们查看生成的 html 报告时不会出现没有 Request 跟 Response 内容的问题。

4、我还遇到过这么一个问题,就是我们有时候会用到 beanshell 脚本,而用到 beanshell 脚本的时候有可能会用到我们自己写的 jar 包。比如我用到一个自己写的获取时间的 jar,当时把这个 jar 包放到了下图目录下

然后通过 maven-resources-plugin 插件把这个包拷贝到 jmeter/lib/ext 目录下当时出错的代码是这样的:

<!-- 把resources文件夹下面的文件拷贝到jmeter/lib/ext目录下 -->
        <plugin>
            <artifactId>maven-resources-plugin</artifactId>
            <executions>
                <execution>
                    <id>copy-resources-lib</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>copy-resources</goal>
                    </goals>
                    <configuration>
                        <!-- test可以在环境变量中定义也可以将下面写成绝对地址 -->
                        <outputDirectory>${project.build.directory}/jmeter/lib/ext</outputDirectory>
                        <resources>
                            <resource>
                                <!-- basedir标识所有工程 -->
                                <directory>${basedir}/src/test/lib</directory>
                                <filtering>true</filtering>
                            </resource>
                        </resources>
                    </configuration>
                </execution>
            </executions>
        </plugin>           

我们的 jar 成功拷贝到 jemter/lib/ext 目录下了,但是执行 jmeter 脚本的时候却报错:

jorphan.reflect.ClassFinder: Can not open the jar D:/eclipse-workspace/vet/target/jmeter/lib/ext/getTime.jar invalid CEN header (bad signature) java.util.zip.ZipException: invalid CEN header (bad signature)
    at java.util.zip.ZipFile.open(Native Method)
    at java.util.zip.ZipFile.<init>(Unknown Source)
    at java.util.zip.ZipFile.<init>(Unknown Source)
    at java.util.zip.ZipFile.<init>(Unknown Source)
    at org.apache.jorphan.reflect.ClassFinder.findClassesInOnePath(ClassFinder.java:435)
    at org.apache.jorphan.reflect.ClassFinder.findClasses(ClassFinder.java:289)
    at org.apache.jorphan.reflect.ClassFinder.findClassesThatExtend(ClassFinder.java:264)
    at org.apache.jorphan.reflect.ClassFinder.findClassesThatExtend(ClassFinder.java:196)
    at org.apache.jmeter.engine.util.CompoundVariable.<clinit>(CompoundVariable.java:71)
    at org.apache.jmeter.engine.util.ValueReplacer.<init>(ValueReplacer.java:44)
    at org.apache.jmeter.engine.PreCompiler.<init>(PreCompiler.java:52)
    at org.apache.jmeter.engine.StandardJMeterEngine.run(StandardJMeterEngine.java:305)
    at java.lang.Thread.run(Unknown Source)

此时百度说我拷贝的 jar 包可能出错,百思不得其解。最后找了一个 md5 校验工具,果不其然,md5 确实跟源文件的 md5 值不一致了。在差点要放弃的时候我把 maven-resources-plugin 插件中一个参数改成了 false。

<!-- 把resources文件夹下面的文件拷贝到jmeter/lib/ext目录下 -->
                            <resource>
                                <!-- basedir标识所有工程 -->
                                <directory>${basedir}/src/test/lib</directory>
                                <filtering>false</filtering>
                            </resource>

最后执行工程,问题得到解决。

5、最近又想起了一个问题,就是我们能不能在构建成功后,如果脚本中有些用例执行失败是否可以发送邮件通知呢?这个是肯定的,然后我这里也写了下怎么触发邮件通知。
首先我们需要配置下 jenkins 全局配置中的邮件通知这个插件,具体配置大概如下图:


然后再到 job 设置中添加 publish performance test result report 插件,输入以下信息:

最后添加插件 Editable Email Notification

特别注意,这里用到了一个邮件模板,所以还需要在 jenkins 安装目录下创建一个 email-templates 目录,然后新建一个文件命名为 testy.template,并输入以下内容:

<STYLE>
BODY, TABLE, TD, TH, P {
  font-family:Verdana,Helvetica,sans serif;
  font-size:11px;
  color:black;
}
h1 { color:black; }
h2 { color:black; }
h3 { color:black; }
TD.bg1 { color:white; background-color:#0000C0; font-size:120% }
TD.bg2 { color:white; background-color:#4040FF; font-size:110% }
TD.bg3 { color:white; background-color:#8080FF; }
TD.test_passed { color:blue; }
TD.test_failed { color:red; }
TD.console { font-family:Courier New; }
</STYLE>
<BODY>

<TABLE>
  <TR><TD align="right"><IMG SRC="${rooturl}static/e59dfe28/images/32x32/<%= (build.result == null || build.result.toString() == 'SUCCESS') ? "blue.gif" : build.result.toString() == 'FAILURE' ? 'red.gif' : 'yellow.gif' %>" />
  </TD><TD valign="center"><B style="font-size: 200%;">BUILD ${build.result ?: 'SUCCESSFUL'}</B></TD></TR>
  <TR><TD>URL</TD><TD><A href="${rooturl}${build.url}">${rooturl}${build.url}</A></TD></TR>
  <TR><TD>Project:</TD><TD>${project.name}</TD></TR>
  <TR><TD>Date:</TD><TD>${it.timestampString}</TD></TR>
  <TR><TD>Duration:</TD><TD>${build.durationString}</TD></TR>
  <TR><TD>Cause:</TD><TD><% build.causes.each() { cause -> %> ${cause.shortDescription} <%  } %></TD></TR>
</TABLE>
<BR/>

<!-- CHANGE SET -->
<% def changeSets = build.changeSets
if(changeSets != null) {
    def hadChanges = false %>
    <TABLE width="100%">
    <TR><TD class="bg1" colspan="2"><B>CHANGES</B></TD></TR>

<%  changeSets.each() { cs_list ->
      cs_list.each() { cs ->
          hadChanges = true %>
        <TR>
          <TD colspan="2" class="bg2">&nbsp;&nbsp;Revision <B><%= cs.metaClass.hasProperty('commitId') ? cs.commitId : cs.metaClass.hasProperty('revision') ? cs.revision :
          cs.metaClass.hasProperty('changeNumber') ? cs.changeNumber : "" %></B> by
            <B><%= cs.author %>: </B>
            <B>(${cs.msgAnnotated})</B>
           </TD>
        </TR>
<%      cs.affectedFiles.each() { p -> %>
        <TR>
          <TD width="10%">&nbsp;&nbsp;${p.editType.name}</TD>
          <TD>${p.path}</TD>
        </TR>
<%      }
      }
  }

    if(!hadChanges) { %>
        <TR><TD colspan="2">No Changes</TD></TR>
<%  } %>
  </TABLE>
<BR/>
<% } %>

<!-- ARTIFACTS -->
<% def artifacts = build.artifacts
if(artifacts != null && artifacts.size() > 0) { %>
  <TABLE width="100%">
    <TR><TD class="bg1"><B>BUILD ARTIFACTS</B></TD></TR>
    <TR>
      <TD>
<%      artifacts.each() { f -> %>
          <li>
            <a href="${rooturl}${build.url}artifact/${f}">${f}</a>
          </li>
<%      } %>
      </TD>
    </TR>
  </TABLE>
<BR/>
<% } %>

<!-- MAVEN ARTIFACTS -->
<%
try {
  def mbuilds = build.moduleBuilds
  if(mbuilds != null) { %>
  <TABLE width="100%">
      <TR><TD class="bg1"><B>BUILD ARTIFACTS</B></TD></TR>
<%
    try {
        mbuilds.each() { m -> %>
        <TR><TD class="bg2"><B>${m.key.displayName}</B></TD></TR>
<%      m.value.each() { mvnbld ->
            def artifactz = mvnbld.artifacts
            if(artifactz != null && artifactz.size() > 0) { %>
      <TR>
        <TD>
<%              artifactz.each() { f -> %>
            <li>
              <a href="${rooturl}${mvnbld.url}artifact/${f}">${f}</a>
            </li>
<%              } %>
        </TD>
      </TR>
<%          }
        }
       }
    } catch(e) {
    // we don't do anything
    }  %>
  </TABLE>
<BR/>
<% }

}catch(e) {
    // we don't do anything
}
%>

<!-- JUnit TEMPLATE -->

<% def junitResultList = it.JUnitTestResult
try {
 def cucumberTestResultAction = it.getAction("org.jenkinsci.plugins.cucumber.jsontestsupport.CucumberTestResultAction")
 junitResultList.add(cucumberTestResultAction.getResult())
} catch(e) {
        //cucumberTestResultAction not exist in this build
}
if (junitResultList.size() > 0) { %>
 <TABLE width="100%">
 <TR><TD class="bg1" colspan="2"><B>${junitResultList.first().displayName}</B></TD></TR>
 <% junitResultList.each{
  junitResult -> %>
     <% junitResult.getChildren().each { packageResult -> %>
        <TR><TD class="bg2" colspan="2"> Name: ${packageResult.getName()} Failed: ${packageResult.getFailCount()} test(s), Passed: ${packageResult.getPassCount()} test(s), Skipped: ${packageResult.getSkipCount()} test(s), Total: ${packageResult.getPassCount()+packageResult.getFailCount()+packageResult.getSkipCount()} test(s)</TD></TR>
        <% packageResult.getFailedTests().each{ failed_test -> %>
          <TR bgcolor="white"><TD class="test_failed" colspan="2"><B><li>Failed: ${failed_test.getFullName()} </li></B></TD></TR>
        <% }
      }
 } %>
 </TABLE>
 <BR/>
<%
} %>

<!-- CONSOLE OUTPUT -->
<% if(build.result==hudson.model.Result.FAILURE) { %>
<TABLE width="100%" cellpadding="0" cellspacing="0">
<TR><TD class="bg1"><B>CONSOLE OUTPUT</B></TD></TR>
<%  build.getLog(100).each() { line -> %>
    <TR><TD class="console">${org.apache.commons.lang.StringEscapeUtils.escapeHtml(line)}</TD></TR>
<%  } %>
</TABLE>
<BR/>
<% } %>

</BODY>

最终邮件截图:

mark 下谢谢!


↙↙↙阅读原文可查看相关链接,并与作者交流