關於測試框架搭建的詳細過程,會在另外一篇文章中詳細介紹: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僅支持三種格式的結果返回: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上自動化的用例執行方式設置爲:自動的
四、上述括號和冒號爲英文字符,且命名中不要出現空格,不然會出現異常
Jmeter做爲測試工具,僅輸出測試腳本,若要造成框架持續集成,須要進行批量調用,而且能夠統一配置腳本的全局參數,如:接口服務器地址、端口、默認登陸用戶名和密碼等;
Ant是Apache軟件基金會JAKARTA目錄中的一個子項目,操做簡單。Ant是由一個內置任務和可選任務組成的。Ant運行時須要一個XML文件(構建文件)。Ant經過調用target樹,就能夠執行各類task。每一個task實現了特定接口對象。因爲Ant構建文件時XML格式的文件,因此和容易維護和書寫,並且結構很清晰。
故直接使用Ant+Jmeter來實現接口測試腳本的批量調用,目前須要解決的問題就是,Jmeter腳本運行時,從Ant獲取HTTP請求默認值中的服務器IP、端口信息,以便後續服務器地址變動後,不會影響接口測試腳本,減小維護的工做量。
主要實現見下圖:
按照圖中的流程配置,每次須要自動運行時,在Jenkins上配置TEST_URL的參數後,接口測試則使用該訪問地址,進行測試,若後續服務器的IP修改後,只須要在Jenkins上配置便可快速完成測試環境的切換。
如下則是XML2TAP.sh的設計思路,根據如下思路輸出sh腳本
具體的實施過程,以下述圖所示:
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
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
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