基於Jmeter和Testlink的自動化測試框架研究與實施

關於測試框架搭建的詳細過程,會在另外一篇文章中詳細介紹:http://www.cnblogs.com/leeboke/p/6145977.htmlphp

 

摘 要html

目前基於Jmeter的接口自動化測試框架,大多隻實現腳本維護和自動調度,沒法與Testlink進行互通,實現測試方案與自動化實施流程鏈接,本文基於Testlink、Jmeter、Jenkins實現:經過Testlink統一維護接口自動化測試用例,Jmeter編寫和運行測試腳本,Jenkins實現統一調度,並返回執行結果和測試報告到Testlink。從而實現整個接口自動化測試框架,提升測試效率,下降後續維護成本。web

關鍵詞: 自動化測試 Jmeter 接口 Testlink Jenkinsexpress

 

一、引言

爲了實現項目快速開發-測試-交付,測試須要在項目開發過程介入,進行接口級別測試,並輸出自動化測試腳本,便於後續集成測試使用自動化測試保證每次BUG修復,沒有引起新的問題,故須要研究一種自動化測試框架,知足目前項目測試的需求bash

二、目標

需求梳理以下:服務器

一、     TestLink統一管理測試用例:因功能測試用例一直在Testlink上維護。框架

二、     接口測試工具知足需求的同時,易上手掌握ide

三、     測試腳本統一維護,如:SVN工具

四、     腳本運行時,須要結合Testlink測試計劃分配用例,將自動化運行結果返回到Testlink。oop

五、     運行失敗的接口,要自動提交到BUG系統

六、      測試結束後,生成測試報告,統計自動化測試整體狀況,並郵件發送給項目相關人員。

七、      測試服務器的IP和端口變化後,在調度測試時統一指定測試IP和端口,避免測試腳本的重複修改

八、      測試框架可擴展到其餘項目。

基於以上需求,梳理大體的目標圖以下:

指望框架達到的效果:

若測試用例已經導入TestLink,且腳本已經設計完成並上傳到SVN,則在web管理平臺構建項目後,便可自動完成全部測試,而且項目可支持定時循環測試。

測試結束後,可自動提交BUG到BUG系統,測試人員只需對最後的結果進行確認便可。

圖中中間部分:雲計算管理平臺接口自動化測試平臺,則是本文應該要實現並達到的效果。

 

三、總體框架

以上是總體框架的思路介紹:

  • TestLink負責接口自動化用例維護
  • Jmeter則負責腳本編寫和運行,統一經過SVN進行維護
  • 另外郵件收發服務器則根據內部狀況靈活選擇
  • 整個過程統一由Jenkins 進行調度管理;
  • BUG系統負責BUG管理,Jmeter腳本執行時,若失敗,自動提交BUG到Build下。

四、技術關鍵

一、Testlink僅支持三種格式的結果返回:Junit、TestNG、TAP ,Jmeter生成報告沒法直接經過Jenkins傳遞給Testlink,並正確識別。

二、TAP格式文件與Testlink中對應用例關聯是經過.tap文件關聯的,即:一個用例就須要一個TAP文件,可是測試結束後須要輸出總體測試結果,必需要求Jmeter測試結束後,輸出一個測試結果文檔,如何將測試結果轉化爲TAP文件,且實現與用例一一對應。

五、實現思路

本章給出第4章提到鍵技術點的解決,在說明前,須要如下前提準備:

一、 規範化Jmeter腳本中的規範要求:

1) HTTP Sample命名規範爲:

2) 若是1條用例對應有多個HTTP Sample,須要使用事務處理器,這時事務處理器名稱必須符合1)的命名規範,事務控制器內的HTTP Sample能夠任意命名,但建議按照:【用例名稱:可修改內容】形式進行命名

3)若是1條用例對應1個HTTP Sample,不強制使用事務處理器

4) 每個HTTP Sample最好都要有斷言,判斷是否執行成功,若沒有斷言,沒法判斷是否符合預期,則轉換腳本默認爲成功。

5)事務控制器內:最好不要再套用事務控制器,若須要建議使用簡單控制器或者一次性控制器等

二、 TestLink上增長自定義字段,用於標記用例與腳本的對應關係,如:AutoTest,AutoTest字段內容則爲:上述中的用例編號,用以將Testlink測試用例與腳本測試結果關聯對應

三、 TestLink上自動化的用例執行方式設置爲:自動的

四、上述括號和冒號爲英文字符,且命名中不要出現空格,不然會出現異常

5.一、腳本批量調用實現思路

Jmeter做爲測試工具,僅輸出測試腳本,若要造成框架持續集成,須要進行批量調用,而且能夠統一配置腳本的全局參數,如:接口服務器地址、端口、默認登陸用戶名和密碼等;

Ant是Apache軟件基金會JAKARTA目錄中的一個子項目,操做簡單。Ant是由一個內置任務和可選任務組成的。Ant運行時須要一個XML文件(構建文件)。Ant經過調用target樹,就能夠執行各類task。每一個task實現了特定接口對象。因爲Ant構建文件時XML格式的文件,因此和容易維護和書寫,並且結構很清晰。

故直接使用Ant+Jmeter來實現接口測試腳本的批量調用,目前須要解決的問題就是,Jmeter腳本運行時,從Ant獲取HTTP請求默認值中的服務器IP、端口信息,以便後續服務器地址變動後,不會影響接口測試腳本,減小維護的工做量。

主要實現見下圖:

按照圖中的流程配置,每次須要自動運行時,在Jenkins上配置TEST_URL的參數後,接口測試則使用該訪問地址,進行測試,若後續服務器的IP修改後,只須要在Jenkins上配置便可快速完成測試環境的切換。

5.二、測試結果回傳思路

如下則是XML2TAP.sh的設計思路,根據如下思路輸出sh腳本

5.二、具體實施過程

具體的實施過程,以下述圖所示:

1. Jenkins每次執行時,首先從SVN指定目錄,檢查是否有用例更新,如有,則下載全部更新測試腳本到工程目錄

2. 經過API key與Testlink創建關聯,並獲取工程配置的測試項目對應測試計劃下的自動化測試用例信息。

3. 調用Ant Plugin插件,經過build.xml配置,執行全部的Jmeter腳本文件,並生成XML格式測試報告:1份。

四、Jmeter腳本執行期間,如有失敗的接口用例,自動提交BUG到BUG系統

5. 對XML測試報告進行二次處理,調用XML2HTML.xsl樣式表,生成HTML格式測試報告,用於郵件發送完整的測試報告和jenkins上發佈測試結果。

6. 對XML測試報告進行轉換,調用XML2TAP.sh腳本,對XML中每個Sample結果進行處理,生成Testlink可識別的TAP文件,以用例爲單位,生成多個tap文件。

7. 將tap文件與第二步中獲取的自動化用例信息對應,返回測試結果及測試報告到Testlink。

8. 將第五步生成的測試報告郵件發送給相關人員。

參考文獻

[1] https://wiki.jenkins-ci.org/display/JENKINS/Integrating+TestLink++Jenkins++JMeter

Jeninks官網

[2] http://blog.csdn.net/wangmuming/article/details/22925127

Jenkins配置

附錄

XML2TAP.sh(推薦:多sample使用:事務處理器)

#!/bin/bash
LANG=zh_CN.UTF-8
export LANG
# Parse each result file from the tests, created by JMeter when run by ant.
# Every file created by JMeter will be parsed consecutively by the following for loop.
# 獲取對應目錄下的xml文件,並經過xpath定位屬性包含lb的全部sample,lb的值即爲jmeter中sample的名稱
for XML_resultfile in $(ls -1 results/*.xml); do
    echo "XML_resultfile: ${XML_resultfile}"
    BASE_filename=$(basename ${XML_resultfile} .xml)  
    TestSamples=( $(xpath  ${XML_resultfile} '//@lb' 2>/dev/null) ) 
    num_of_TestSamples=${#TestSamples[@]}
     echo "num_of_TestSamples: ${num_of_TestSamples}"
  # Reference to TAP13 file standard:
  # http://podwiki.hexten.net/TAP/TAP13.html?page=TAP13
  # 每個testSample進行處理
  if [ ${num_of_TestSamples} -gt 0 ]; then
      for ((line=0; ${line} < ${num_of_TestSamples}; line++)); do    
        echo "------------------CASE ${line}--------------------------"    
        echo "TestCases fullname: ${TestSamples[${line}]}"
        #判斷sample的命名格式,是否符合x-x-x(x)x,其中x表示任意字符
        #舊規則,此處去除:限制isFlag=$( echo ${TestSamples[${line}]} | grep '.*-.*-.*(.\{1,\}):.*')
        isFlag=$( echo ${TestSamples[${line}]} | grep '.*-.*-.*(.\{1,\}).*')
        if [ "${isFlag}" != "" ]; then
            #截取左括號以前的做爲tap文件名稱,與testlink自定義字段AutoTest中填寫的內容一致
            filename=$( echo ${TestSamples[${line}]} | cut -d '"' -f 2 | cut -d '(' -f 1 )
            TAP_filename=${filename}.tap
            isExistTap=""
            #判斷tap文件是已存在,若已存在,直接跳過,由於:有些用例測試須要多個sample配合完成,因此在第一個sample處理時,已經有結果,後面的能夠直接跳過
            isExistTap=$(ls -R | grep "${TAP_filename}")
            if [ "${isExistTap}" == "" ]; then
                #TestCase=$( echo ${TestSamples[${line}]} | cut -d '"' -f 2 | cut -d ':' -f 1 )
                TestCase=$( echo ${TestSamples[${line}]} | cut -d '"' -f 2)
                num_of_testcases=1        
                #獲取事務控制器的result message,以提取sample總數和失敗總數
                transaction_rm=($(xpath ${XML_resultfile} "//sample[contains(@rm,'Number of samples in transaction') and contains(@lb,'${filename}')]/@rm" 2>/dev/null))
                # 此處對使用事務控制器的用例進行處理:若事務處理器,子採樣器名稱不須要按照規範編號,可是事務處理器必須符合                
                echo "----type:transaction---num_of_transactionSamples: ${#transaction_rm[@]}"
                echo "num_of_transactionSamples,正常值[0,15]: ${#transaction_rm[@]}"                
                if [ ${#transaction_rm[@]} -gt 0 -a ${#transaction_rm[@]} -lt 20 ]; then
                    #獲取事務下總Sanple個數,截取rm字段的結果
                    num_of_transactionSamples=$( echo ${transaction_rm[6]} | cut -d ',' -f 1 )
                    #echo "num_of_transactionSamples: ${num_of_transactionSamples}"
                    #獲取事務下失敗的總個數,截取rm字段的結果
                    num_of_transactionFailSamples=$( echo ${transaction_rm[12]} | cut -d '"' -f 1 )
                    #echo "num_of_FailTestSamples: ${num_of_transactionFailSamples}"                    
                    ((num_of_testcases=${num_of_transactionSamples}))
                    ((num_of_resultif_count=${num_of_transactionFailSamples}))                                    
                #此處對發現有多個事務控制器同名的狀況進行處理
                elif [ ${#transaction_rm[@]} -gt 20 ]; then    
                    echo "發現至少兩條用例編號相同:${filename}"    
                    continue
                #此處對未使用事務控制器的sample或者httpSample進行處理
                else
                    echo "----sample-------"
                    #在xml中定位到要處理的用例有多少個httpSample、sample、通常jdbc鏈接的結果爲sample,故須要分別處理,兩者相加,則爲用例個數
                    TestCase_count1=( $(xpath ${XML_resultfile} "//httpSample[contains(@lb,'${filename}')]/@sc" 2>/dev/null) )
                    TestCase_count2=( $(xpath ${XML_resultfile} "//sample[contains(@lb,'${filename}')]/@sc" 2>/dev/null) )
                    ((num_of_testcases=${#TestCase_count1[@]}+${#TestCase_count2[@]}))
                    # 同獲取用例個數,此處查看斷言是否有失敗的,以判斷用例是否OK,一樣須要httpSample和sample分別處理,若num_of_resultif_count=0表示執行結果OK                    
                    result_if1=( $(xpath ${XML_resultfile} "//httpSample[contains(@lb,'${filename}')]//assertionResult[failure='true']/failure" 2>/dev/null | tr -d '"' | tr ' ' '_' | sed 's/^_//') )
                    result_if2=( $(xpath ${XML_resultfile} "//sample[contains(@lb,'${filename}')]//assertionResult[failure='true']/failure" 2>/dev/null | tr -d '"' | tr ' ' '_' | sed 's/^_//') )
                    ((num_of_resultif_count=${#result_if1[@]}+${#result_if2[@]}))
                fi
                echo "TestCase:${TestCase}"
                echo "filename:${TAP_filename}"
                echo "TestCase_count:${num_of_testcases}"
                echo ">>>${BASE_filename}.xml Covert to : ${TAP_filename}"                
                #輸出基礎內容到tap文件                
                label="${BASE_filename} - ${TestCase}"                            
                echo "TAP version 13" > results/${TAP_filename}                
                echo "1..${num_of_testcases}" >> results/${TAP_filename}                
                #echo "result_if1:${#result_if1[@]} -- result_if2:${#result_if2[@]}"
                echo ">TestResult:${num_of_resultif_count}"
                #對於執行失敗的用例,獲取失敗信息,並輸出到結果文件
                if [ ${num_of_resultif_count} -gt 0 ]; then
                    #獲取失敗的斷言信息
                    msg_texts=( $(xpath ${XML_resultfile} "//httpSample[contains(@lb,'${filename}')]//assertionResult[failure='true']/failureMessage" 2>/dev/null | tr -d '"' | tr ' ' '_' | sed 's/^_//') )
                    result="not ok ${num_of_testcases}  ${label} - error[${num_of_resultif_count}]"
                    echo "${result}">> results/${TAP_filename}
                    echo "failureMessage:${msg_texts}">> results/${TAP_filename}                    
                else            
                    result="ok ${num_of_testcases}  ${label} - error[${#result_if[@]}]"
                    echo "${result}">> results/${TAP_filename}
                    #echo "${result}"
                    #(( line=${line}+${num_of_testcases}-1 ))                                                
                fi
            else
                echo ">tap already converted"
            fi
        else
            echo "sample name  is not standard"
        fi
    done
    else
        echo "over" >> results/${TAP_filename}
    fi
done
View Code

XML2TAP.sh(多sample:按sample名稱一致處理)

 1 #!/bin/bash
 2 LANG=zh_CN.UTF-8
 3 export LANG
 4 # Parse each result file from the tests, created by JMeter when run by ant.
 5 # Every file created by JMeter will be parsed consecutively by the following for loop.
 6 # 獲取對應目錄下的xml文件,並經過xpath定位屬性包含lb的全部sample,lb的值即爲jmeter中sample的名稱
 7 for XML_resultfile in $(ls -1 results/*.xml); do
 8     echo "XML_resultfile: ${XML_resultfile}"
 9     BASE_filename=$(basename ${XML_resultfile} .xml)  
10     TestSamples=( $(xpath  ${XML_resultfile} '//@lb' 2>/dev/null) ) 
11     num_of_TestSamples=${#TestSamples[@]}
12      echo "num_of_TestSamples: ${num_of_TestSamples}"
13   # Reference to TAP13 file standard:
14   # http://podwiki.hexten.net/TAP/TAP13.html?page=TAP13
15   # 每個testSample進行處理
16   if [ ${num_of_TestSamples} -gt 0 ]; then
17       for ((line=0; ${line} < ${num_of_TestSamples}; line++)); do    
18         echo "------------------CASE ${line}--------------------------"    
19         echo "TestCases fullname: ${TestSamples[${line}]}"
20         #判斷sample的命名格式,是否符合x-x-x(x):x,其中x表示任意字符
21         isFlag=$( echo ${TestSamples[${line}]} | grep '.*-.*-.*(.\{1,\}):.*')
22         if [ "${isFlag}" != "" ]; then
23             #截取左括號以前的做爲tap文件名稱,與testlink自定義字段AutoTest中填寫的內容一致
24             filename=$( echo ${TestSamples[${line}]} | cut -d '"' -f 2 | cut -d '(' -f 1 )
25             TAP_filename=${filename}.tap
26             isExistTap=""
27             #判斷tap文件是已存在,若已存在,直接跳過,由於:有些用例測試須要多個sample配合完成,因此在第一個sample處理時,已經有結果,後面的能夠直接跳過
28             isExistTap=$(ls -R | grep "${TAP_filename}")
29             if [ "${isExistTap}" == "" ]; then
30                 TestCase=$( echo ${TestSamples[${line}]} | cut -d '"' -f 2 | cut -d ':' -f 1 )
31                 num_of_testcases=1
32                 #在xml中定位到要處理的用例有多少個httpSample、sample、通常jdbc鏈接的結果爲sample,故須要分別處理,兩者相加,則爲用例個數
33                 TestCase_count1=( $(xpath ${XML_resultfile} "//httpSample[contains(@lb,'${filename}')]/@rc" 2>/dev/null) )
34                 TestCase_count2=( $(xpath ${XML_resultfile} "//sample[contains(@lb,'${filename}')]/@rc" 2>/dev/null) )
35                 ((num_of_testcases=${#TestCase_count1[@]}+${#TestCase_count2[@]}))
36                 echo "TestCase:${TestCase}"
37                 echo "filename:${TAP_filename}"
38                 echo "TestCase_count:${num_of_testcases}"        
39                 label="${BASE_filename} - ${TestCase}"    
40                 #輸出基礎內容到tap文件                
41                 echo "TAP version 13" > results/${TAP_filename}
42                 echo ">>>${BASE_filename}.xml Covert to : ${TAP_filename}"
43                 echo "1..${num_of_testcases}" >> results/${TAP_filename}
44                 # 同獲取用例個數,此處查看斷言是否有失敗的,以判斷用例是否OK,一樣須要httpSample和sample分別處理,若num_of_resultif_count=0表示執行結果OK
45                 result_if1=( $(xpath ${XML_resultfile} "//httpSample[contains(@lb,'${filename}')]/assertionResult[failure='true']/failure" 2>/dev/null | tr -d '"' | tr ' ' '_' | sed 's/^_//') )
46                 result_if2=( $(xpath ${XML_resultfile} "//sample[contains(@lb,'${filename}')]/assertionResult[failure='true']/failure" 2>/dev/null | tr -d '"' | tr ' ' '_' | sed 's/^_//') )
47                 ((num_of_resultif_count=${#result_if1[@]}+${#result_if2[@]}))
48                 echo ">TestResult:${num_of_resultif_count}"
49                 if [ ${num_of_resultif_count} -gt 0 ]; then
50                     #獲取失敗的斷言信息
51                     msg_texts=( $(xpath ${XML_resultfile} "//httpSample[contains(@lb,'${filename}')]/assertionResult[failure='true']/failureMessage" 2>/dev/null | tr -d '"' | tr ' ' '_' | sed 's/^_//') )
52                     result="not ok ${num_of_testcases}  ${label} - error[${num_of_resultif_count}]"
53                     echo "${result}">> results/${TAP_filename}
54                     echo "failureMessage:${msg_texts}">> results/${TAP_filename}                    
55                     else                
56                         if [ ${num_of_testcases} -gt 0 ]; then
57                             result="ok ${num_of_testcases}  ${label} - error[${#result_if[@]}]"
58                             echo "${result}">> results/${TAP_filename}
59                             #echo "${result}"
60                             #(( line=${line}+${num_of_testcases}-1 ))
61                         else
62                             result="not ok 1 - ${num_of_testcases}  ${label} - error[XML Covert to TAP ERROR:Xpath query find 0 result]"
63                             echo "${result}">> results/${TAP_filename}
64                             #echo "${result}"
65                             #(( line=${line}+${num_of_testcases} ))
66                         fi                                    
67                 fi
68             else
69                 echo ">tap already converted"
70             fi
71         else
72             echo "sample name  is not standard"
73         fi
74     done
75     else
76         echo "over" >> results/${TAP_filename}
77     fi
78 done
View Code

build.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project name="ant-jmeter-test" default="run" basedir=".">
 3     <tstamp> <format property="time" pattern="yyyyMMddhhmm" />    </tstamp>
 4     <tstamp> <format property="report.datestamp" pattern="yyyy/MM/dd HH:mm" /></tstamp>
 5     <property environment="env"/>
 6     <property name="report.title" value="接口測試"/>   
 7     <property name="jmeter.home" value="${env.JMETER_HOME}" /><!-- 須要改爲本身本地的 Jmeter 目錄-->     
 8     <property name="jmeter.result.dir" value="${env.WORKSPACE}/results" /><!-- jmeter測試結果放置目錄,這裏是放在構建項目下的results目錄-->
 9     <property name="jmeter.sum.dir" value="${env.WORKSPACE}/sum" /><!-- jmeter測試概要放置目錄(主要用於郵件發送時,做正文內容),這裏是放在構建項目下的sum目錄-->
10     <property name="testplan.dir" value="${env.WORKSPACE}" /><!-- 運行的jmx腳本目錄-->
11    
12    
13     <property name="ReportName" value="TestReport" /><!-- 生成的報告的前綴 -->
14     <property name="jmeter.result.jtlName" value="${jmeter.result.dir}/${ReportName}.xml" /><!-- 生成的jtl報告名稱 -->
15     <property name="jmeter.result.htmlName" value="${jmeter.result.dir}/${ReportName}.html" /><!-- 生成的html報告名稱 -->
16     <property name="jmeter.sum.htmlName" value="${jmeter.sum.dir}/sum.html" />     <!-- 生成的sum報告的名稱 -->    
17 
18     <target name="run">
19         <echo message="start..."/>
20         <antcall target="clean" />
21         <antcall target="test" />
22         <antcall target="report" />
23     </target>
24     <target name="clean">
25         <delete dir="${env.WORKSPACE}/results/" />
26         <mkdir dir="${env.WORKSPACE}/results" />
27         <delete dir="${env.WORKSPACE}/sum/" />
28         <mkdir dir="${env.WORKSPACE}/sum" />        
29     </target>
30     <target name="test">
31         <taskdef name="jmeter" classname="org.programmerplanet.ant.taskdefs.jmeter.JMeterTask" />
32         <jmeter jmeterhome="${jmeter.home}" resultlog="${jmeter.result.jtlName}">
33             <!-- 聲明要運行的腳本。"*.jmx"指包含此目錄下的全部jmeter腳本 -->
34             <!-- 配置構建參數TESTLINK_TESTCASE_TESTFILEPATH在Jenkins上    -->
35             <!-- ant -DGUI=false -DTEST_ENVIRONMENT=2 -DTESTLINK_TESTCASE_TESTFILEPATH=test.jmx -->
36             <testplans dir="${testplan.dir}" includes="${env.TESTLINK_TESTCASE_TESTFILEPATH}.jmx" />
37             <property name="jmeter.save.saveservice.output_format" value="xml"/>
38             <property name="jmeter.save.saveservice.assertion_results" value="all"/>
39             <property name="jmeter.save.saveservice.bytes" value="true"/>             
40         </jmeter>
41     </target>
42      <path id="xslt.classpath">
43         <fileset dir="${jmeter.home}/lib" includes="xalan*.jar"/>
44         <fileset dir="${jmeter.home}/lib" includes="serializer*.jar"/>
45      </path>
46 
47       <target name="report">
48        
49         <xslt  classpathref="xslt.classpath"
50                 force="true"
51                 in="${jmeter.result.jtlName}" 
52                 out="${jmeter.result.htmlName}" 
53                 style="${jmeter.home}/extras/jmeter.results.shanhe.me.xsl"> <!-- jtl測試報告轉化爲html報告,下載地址:http://shanhe.me/download.php?file=jmeter.results.shanhe.me.xsl-->
54         <param name="dateReport" expression="${report.datestamp}"/>
55         </xslt>
56         <xslt  classpathref="xslt.classpath"
57                 force="true"
58                 in="${jmeter.result.jtlName}" 
59                 out="${jmeter.sum.htmlName}" 
60                 style="${jmeter.home}/extras/jmeter-results-detail-report_21-lj.xsl"> <!-- sum.html報告,該xsl文件在jmeter-results-detail-report_21這個文件的body中僅保留summary部分的顯示,用於jenkins構建後郵件發送的正文展現 -->
61         <param name="dateReport" expression="${report.datestamp}"/>
62         </xslt>
63         <!-- 由於上面生成報告的時候,不會將相關的圖片也一塊兒拷貝至目標目錄,因此,須要手動拷貝 -->
64         <copy todir="${jmeter.result.dir}">
65             <fileset dir="${jmeter.home}/extras">
66                 <include name="collapse.png"/>
67                 <include name="expand.png"/>
68             </fileset>
69         </copy>
70     </target>
71 </project>
72  
View Code
相關文章
相關標籤/搜索