前言
關於測試領域的自動化,已有很多的文章做過介紹,“黑科技”也比比皆是,如通過Java位元組碼技術實現介面的錄製,Fiddler錄製內容轉Python指令碼,App中的插樁除錯等,可見角度不同,對最佳實踐的理解也不一樣。這裡想要闡述的是,外賣(上海)QA團隊應用相對“小眾”的Ruby,在資源有限的條件下實現自動化測試的一些實踐與經驗分享。
背景
加入外賣上海團隊時,共2名QA同學,分別負責App與M站的功能測試,自動化測試停留在學習北京側介面測試框架的階段,實效上近乎為0,能力結構上在程式碼這部分是明顯薄弱的。而擺在面前的問題是,迴歸測試的工作量較大,特別是M站渠道眾多(4個渠道),移動端API的介面測試需區分多個版本,自動化測試的開展勢在必行。在這樣的條件下,如何快速且有效地搭建並推廣自動化測試體系?在過去對自動化測試的多種嘗試及實踐的總結後,選擇了Ruby。
Why Ruby?
簡單點說就是:並不聰明的大腦加上“好逸惡勞”的思想,促使我在這些年的自動化測試實踐中,不斷尋找更合適的解決方案。所謂技術,其本質都是站在別人的肩膀上,肩膀的高度也決定了實現目標的快慢,而Ruby正符合所需的一些特徵:
- 效率。自身應該算是“純粹”的測試人員,在“測試開發”這重職業並不普及的年代,一直希望有種語言可以讓測試的開發效率超過研發,Ruby做到了;
- 人性化的語法,各種糖。類似1.day.ago,簡單的表達不需要解釋;
- 強大的超程式設計能力。基於此,DHH放棄了PHP而使用Ruby開發出了Rails,DSL也因此成為Ruby開發的框架中非常普通的特性,而這對於很多主流語言都是種奢望;
- 對於測試來說足夠充足的社群資源。不涉及科學計算,不涉及服務開發,在沒有這些需求的情況下,Python和Java不再是必需。
脫離了開發語言的平臺,但在不關注白盒測試的情況下並無太多不妥。當Ruby用於測試開發,基本“遮蔽”了效能上的劣勢,充分展現了敏捷、易用的特點,也是選擇這一技術路線的主要因素。
介面自動化框架Coral-API
框架思路
介面自動化測試方案眾多,個人認為它們都有自己的適用的範圍和優缺點。UI類工具雖輕鬆實現無碼Case,但在處理介面變動和全鏈路介面流程上多少會顯得有些繁瑣(尤其在支援資料驅動需求下),過多的規則、變數設定和編碼也相差無幾;錄製型別的方案,更多還是適合迴歸,對於較全面的介面測試也需要一定的開發量。基於這些權衡考慮,採用一種編碼儘可能少、應用面更廣的介面自動化框架實現方式,把它命名為Coral-API,主要有以下特點:
-
測試資料處理獨立
- 預先生成測試所需的最終資料,區分單介面測試資料(單介面資料驅動測試)與鏈路測試資料
- 通過命令列形式的語句解決了引數的多層巢狀及動態資料生成的問題
- Excel中維護測試資料,最終轉化為YML或存入DB,折中解決了JSON形式的資料難維護問題
-
學習成本低
- 框架提供生成通用結構程式碼的功能,使測試人員更關注於業務邏輯處理
- DSL的書寫風格,即便沒有Ruby的語言基礎,也可以較快掌握基本的介面測試用例編寫
-
擴充套件性
- 支援Java平臺的擴充套件
- 支援HTTP/RPC介面,可根據開發框架擴充套件
- 框架基於Rspec,支援多種驗證方式(Build-In Matcher),及支援自定義Matcher,目前實現了JSON去噪的Diff,各種複合的條件比較
以單個介面測試編寫為例,下圖描述了具體流程:
從圖中可以看到,安裝了Coral-API的gem後,可通過命令列 “coral g {apiname}” ,通過模板來生成測試資料XLS及對應的資料處理檔案(例如ApiOne.rb檔案),修改並執行ApiOne.rb檔案,則可以生成最終的測試資料(YML檔案)及測試類和Case檔案。如果開發框架支援(有途徑可解析出引數),則可以通過指令碼直接生成整個服務下所有介面的測試程式碼,實現自動化Case的同步開發。這種處理過程主要是一併解決了以下幾個問題:
- 複雜結構的測試資料構造
- 動態引數的賦值
- 測試資料的維護
- 測試資料的載入
假設有以下這樣一個介面請求格式,包含一個orderInfo的子節點,及payInfo的list,還需要解決一些變化值的問題,如各種id和time(暫且稱為動態欄位)。一般框架中會以JSON格式來作為測試用例的請求格式,在程式碼中按變數處理動態欄位值。JSON作為請求資料的儲存形式,存在一個很大的問題,就是後期維護,尤其是Case數量較多的時候。因此,考慮仍以Excel為資料維護的初始形式(使用上更直觀),通過Sheet的巢狀來處理複雜結構,也便於後期介面引數變動後的Case維護。
userId: E000001
requestId: '1938670097'
orderInfo:
orderId: '6778043386'
count: '2'
name: testgoods
payInfo:
- transactionId: '510455433082284'
payTime: '2017-04-04 13:03:34'
payType: BOC
- transactionId: '167338836018587'
payTime: '2017-04-04 13:03:34'
payType: Wallet
createTime: '2017-04-04 13:03:34'
複製程式碼
測試資料的Excel做如下設計,Main中為第一層引數結構,預期響應另分一個Sheet,子節點和list節點的內容寫在對應的Sheet中,動態值均置為空,在介面資料類中處理,orderInfo節點和payInfo節點均另寫在新的Sheet中,用於單介面資料驅動的Case與鏈路迴歸用Case分開,當然這會增加一些Case維護的成本,可以選擇是否區分。
示例的資料結構,通過以下語句即可實現,如果需要為後續介面測試提供前置步驟的資料,也可以同步實現,下例中為後續介面生成了5條請求資料。針對介面引數變動的情況,可以修改Excel和資料處理類檔案,執行一遍即可,也提供了批量重新生成所有介面資料的指令碼。
class Demo < ApiCaseBase
update self.request,:requestId=>'gen_randcode(10)',:createTime=>'get_datetime'
add_node self.request,"orderInfo",:orderId=>'gen_randcode(10)'
add_list self.request,"payInfo",:transactionId=>'gen_randcode(15)',:payTime=>'get_datetime'
sheetData={'ForApiOther'=>5}
generate_data self,sheetData do
update_force @data,:orderId=>'gen_randcode(10)',:createTime=>'get_datetime'
add_node_force @data,"orderInfo",:orderId=>'gen_randcode(10)'
add_list_force @data,"payInfo",:transactionId=>'gen_randcode(15)',:payTime=>'get_datetime'
end
end
複製程式碼
Excel作為Case的維護形式,缺點是Case較多情況下頻繁讀取比較影響時間。在這種情況下,考慮到把資料序列化到YML中,啟動執行時介面測試類自動與測試資料進行繫結。在Case中可以直接使用形如 DemoTest.request[1]的請求資料,提高了速度,結構上也清晰了不少。
介面測試類檔案(HTTP介面呼叫為例)生成的模板如下,修改對應的介面資訊即可,支援DB驗證(程式碼塊p這部分是目前唯一需要寫Ruby程式碼的地方,當然這是非必需項)。
require 'apicasebase'
class PreviewTest
include ApiTestBase
set_cookie
set_domain "Domain_takeaway"
set_port 80
set_path "/waimai/ajax/wxwallet/Preview"
set_method "get"
set_sql "select * from table"
p = proc do |dbres|
# do something
# return a hash
end
set_p p
end
複製程式碼
TestCase檔案如下,原則上無需修改,只需要在測試資料的Excel中編寫匹配規則及預期輸出,基本上實現了單個介面無編碼的資料驅動測試。
require 'Preview_validate'
RSpec.shared_examples "Preview Example" do |key,requestData,expData|
it 'CaseNo'+ key.to_s + ': '+expData['memo'] do
response = PreviewTest.response_of(key)
expect(response).to eval("#{expData['matcher']} '#{expData['expection']}'")
end
end
RSpec.describe "Preview介面測試",:project=>'api_m_auto',:author=>'Neil' do
PreviewTest.request.each{|key,parameter|include_examples "Preview Example",key,PreviewTest.request[key],PreviewTest.expect[key]}
end
複製程式碼
介面流程Case編寫就是各獨立介面的業務邏輯串聯,重點是Case的組織,把一些公用的Steps獨立出shared_examples,在主流程的Case中include這些shared_examples即可,關聯的上下游引數 通過全域性變數來傳遞。
RSpec.describe "業務流程測試" ,:project=>'api_m_auto',:author =>'Neil' do
let(:wm_b_client) { WmBClient.new('自配') }
before(:context) do
init_step
end
context "線上支付->商家接單->確認收貨->評價" do
include_examples "OrderAndPay Example",1
include_examples "AcceptOrder Example"
include_examples "CommentStep Example"
end
end
複製程式碼
通過上面的介紹,可以看到,Case的編寫大部分可以通過程式碼生成實現(熟悉以後部分介面也可以根據需要進行操作步驟的取捨,如直接編寫YML)。實踐下來的情況是,從各方面一無所有,17個人日左右的時間,完成了M站API層介面自動化(業務流程9個,單個介面10個)及點評外賣移動端API的介面自動化(業務流程9個,單個介面20個),實現了外賣業務全鏈路介面迴歸,平均每個業務流Case步驟9個左右。期間也培養了一名之前未接觸過Ruby的同學,在完成了第一版開發後,兩名初級階段的同學逐步承擔起了框架的改進工作,實現了更多有效的驗證Matcher,並支援了移動端API多版本的測試。之後的迴歸測試不僅時間上縮減了50%以上,也通過介面自動化3次發現了問題,其中一次API不同版本導致的Bug充分體現了自動化測試的效率。通過ci_reporter,可以方便地將Rspec的報告格式轉為JUnit的XML格式,在Jenkins中做對應的展示。
解決介面多版本測試的例子
移動端API自動化中存在的問題就是,一個介面會存在多個版本並存的情況,有header中內容不同的,或formdata內容不同的情況,在介面迴歸中必須都要照顧到,在Coral-API中我們採用以下方式進行處理。
在config.yml中定義各版本的header。
Domain_takeaway_header:
v926: '{"connection":"upgrade","x-forwarded-for":"172.24.121.32, 203.76.219.234","mkunionid":"-113876624192351423","pragma-apptype":"com.dianping.ba.dpscope","mktunneltype":"tcp","pragma-dpid":"-113876624192351423","pragma-token":"e7c10bf505535bfddeba94f5c050550adbd9855686816f58f0b5ca08eed6acc6","user-agent":"MApi 1.1 (dpscope 9.4.0 appstore; iPhone 10.0.1 iPhone9,1; a0d0)","pragma-device":"598f7d44120d0bf9eb7cf1d9774d3ac43faed266","pragma-os":"MApi 1.1 (dpscope 9.2.6 appstore; iPhone 10.0.1 iPhone9,1; a0d0)","mkscheme":"https","x-forwarded-for-port":"60779","X-CAT-TRACE-MODE":"true","network-type":"wifi","x-real-ip":"203.76.219.234","pragma-newtoken":"e7c10bf505535bfddeba94f5c050550adbd9855686816f58f0b5ca08eed6acc6","pragma-appid":"351091731","mkoriginhost":"mobile.dianping.com","pragma-unionid":"91d9c0e21aca4170bf97ab897e5151ae0000000000040786871"}'
v930: '{"connection":"upgrade","x-forwarded-for":"172.24.121.32, 203.76.219.234","mkunionid":"-113876624192351423","pragma-apptype":"com.dianping.ba.dpscope","mktunneltype":"tcp","pragma-dpid":"-113876624192351423","pragma-token":"e7c10bf505535bfddeba94f5c050550adbd9855686816f58f0b5ca08eed6acc6","user-agent":"MApi 1.1 (dpscope 9.4.0 appstore; iPhone 10.0.1 iPhone9,1; a0d0)","pragma-device":"598f7d44120d0bf9eb7cf1d9774d3ac43faed266","pragma-os":"MApi 1.1 (dpscope 9.3.0 appstore; iPhone 10.0.1 iPhone9,1; a0d0)","mkscheme":"https","x-forwarded-for-port":"60779","X-CAT-TRACE-MODE":"true","network-type":"wifi","x-real-ip":"203.76.219.234","pragma-newtoken":"e7c10bf505535bfddeba94f5c050550adbd9855686816f58f0b5ca08eed6acc6","pragma-appid":"351091731","mkoriginhost":"mobile.dianping.com","pragma-unionid":"91d9c0e21aca4170bf97ab897e5151ae0000000000040786871"}'
......
複製程式碼
在介面測試類被載入時會進行全域性變數賦值,同時替換header裡對應節點的token,測試資料YML檔案中則做這樣的描述,每條資料的header則較方便地被替換。
---
Main:
1: &DEFAULT
headers: '<%= $v926 %>'
host: mobile.51ping.com
port: '80'
path: "/deliveryaddresslist.ta"
search: "?geotype=2&actuallat=31.217329&actuallng=121.415603&initiallat=31.22167778439444&initiallng=121.42671951083571"
method: GET
query: '{"geotype":"2","actuallat":"31.217329","actuallng":"121.415603","initiallat":"31.22167778439444","initiallng":"121.42671951083571"}'
formData: "{}"
scheme: 'http:'
2:
<<: *DEFAULT
headers: '<%= $v930 %>'
3:
<<: *DEFAULT
headers: '<%= $v940 %>'
4:
<<: *DEFAULT
headers: '<%= $v950 %>'
5:
<<: *DEFAULT
headers: '<%= $v990 %>'
複製程式碼
解決RPC介面測試
HTTP介面的測試框架選擇面還是比較多的,RPC呼叫的框架如何測試呢?答案就是JRuby + Java的反射呼叫,在Pigeon介面中我們已經試點了這種方式,證明是可行的,針對不同的RPC框架實現不同的Adapter(Jar檔案),Coral-API傳參(JSON格式)給Adapter,Adapter通過解析引數進行反射呼叫,這樣對於框架來說無需改動,只需對部分檔案模板稍作調整,也無需在Ruby中混寫Java程式碼,實現了最少的程式碼量—2行。
UI自動化框架Coral-APP
框架思想
App的UI自動化,Ruby的簡便性更明顯,尤其Appium提供了對Ruby良好的支援,各種UI框架的優劣就不在此贅述了。綜合比較了Appium與Calabash後,選擇了前者,測試框架選用了更適合業務流描述的Cucumber,沿用了以前在Web自動化中使用的物件庫概念,將頁面元素儲存在CSV中,包括了Android與iOS的頁面物件描述,滿足不同系統平臺的測試需要。在針對微信M站的UI自動化方案中,還需解決微信WebView的切換,及多視窗的切換問題,appium_lib都提供了較好的支援,下面介紹下結合了Appium及Cucumber的自動化框架Coral-APP。
框架結構如下圖:
step_definitions目錄下為步驟實現,public_step.rb定義了一些公共步驟,比如微信測試需要用到的上下文切換,Webview裡的頁面切換功能,也可以通過support目錄下的global_method.rb裡新增的Kernel中的方法來實現。
support/native目錄下為app測試的配置檔案,support/web目錄下為h5測試的配置檔案。
support/env.rb 為啟動檔案,主要步驟如下:
$caps = Appium.load_appium_txt file: File.expand_path('../app/appium.txt', __FILE__), verbose: true
$caps[:caps].store("chromeOptions",{"androidProcess":"com.tencent.mm:tools"})
$driver = Appium::Driver.new($caps,true)
Elements.generate_all_objects
Before{$driver.start_driver}
After{$driver.quit_driver}
複製程式碼
support/elements下為物件庫CSV檔案,內容如下圖:
support/elements.rb為物件庫實現,將CSV中的描述轉換為Elements模組中物件的功能,這樣在Page中就可以直接使用類似“Elements.微信我” 這樣的物件描述了。
......
def self.define_ui_object(element)
case $caps[:caps][:platformName].downcase
when "android"
idempotently_define_singleton_method(element["OBJNAME"]){$driver.find_element(:"#{element["ATTRIBUTE"]}","#{element["ANDROID_IDENTITY"]}")}
else
idempotently_define_singleton_method(element["OBJNAME"]){$driver.find_element(:"#{element["ATTRIBUTE"]}","#{element["IOS_IDENTITY"]}")}
end
end
......
複製程式碼
support/pages為Page層,實現了每個頁面下的操作,目前把它實現為Kernel中的方法,採用中文命名,便於閱讀使用。
module Kernel
def 點選我
Elements.微信我.click
end
def 點選收藏按鈕
Elements.微信收藏.click
end
def 點選收藏項
Elements.微信收藏連結.click
end
def 點選收藏中的美團外賣連結
Elements.微信收藏連結URL.click
end
end
複製程式碼
step裡的步驟我們可以這樣寫,封裝好足夠的公共步驟或方法,Case的編寫就是這麼簡單。
When /^進入美團外賣M站首頁$/ do
點選我
點選收藏按鈕
點選收藏項
點選收藏中的美團外賣連結
等待 5
step "切換到微信Webview"
等待 15
step "切換到美團外賣window"
end
複製程式碼
最終Feature內容如下:
Feature: 迴歸下單主流程
開啟微信->進入首頁->定位->進入自動化商戶->下單->支付->訂單詳情
Scenario:
When 進入美團外賣M站首頁
複製程式碼
相對於其他的UI測試框架,使用接近自然語言的描述,提高了Case可讀性,編寫上也沒有其他框架那麼複雜。當然UI自動化中還是有一些小難點的,尤其是Hybrid應用,Appium目前還存在些對使用影響不大的Bug,在框架試用完成的情況下,將在微信入口體驗優化專案結束後的進一步使用中去總結與完善。
質量工作的自動化
都知道在美團點評,QA還擔負著質量控制的工作,當功能+自動化+效能+其他測試工作於一身,而且是1:8的測試開發比下,如何去關注質量的改進?答案只有:工具化、自動化。開發這樣一個小系統,技術方案選擇上考慮主要是效率和學習成本,符合敏捷開發的特點,基於這些因素,應用了被稱為“Web開發的最佳實踐”的Rails框架。
Rails的設計有些顛覆傳統的程式設計理念,CRUD的實現上不用說了,一行命令即可,資料庫層的操作,通過migration搞定,在Mail,Job等功能的實現上也非常方便,框架都有對應的模組,並且提供了大量的元件,Session、Cookie、安全密碼、郵件地址校驗都有對應的gem,感覺不像是在寫程式碼,更像是在配置專案,不知不覺,一個系統雛形就完成了,整理了下專案中使用到的gem,主要有以下這些。
前端相關:
- bootstrap-sass Bootstrap框架
- jquery-rails jQuery框架
- simple_form 優化的form元件
- chartkick 堪稱一行程式碼即可的圖表元件
- hightchart 圖表元件
後端相關:
- validates_email_format_of 郵件地址校驗
- has_secure_password 安全密碼元件
- mysql2 MySQL連線元件
- cancancan 許可權管理元件
- sidekiq 佇列中介軟體
- sidekiq-cron 定時Job元件
- rest-client Http And Rest Client For Ruby
- will_paginate 分頁元件
從搭建開發環境、寫Demo,自己做產品、開發、測試、搭建生產環境、部署,邊參閱文件邊實現,總共18個人日左右,實現了平臺基礎功能、線上故障問題的管理及通知、測試報告的管理及通知、Sonar資料的抽取(Job及郵件)、Bug資料的抽取(Job)、自動化測試專案的接入、質量資料的Dashboard各類資料圖表展示等功能,以下為系統功能的兩個示例:
後臺管理介面
線下缺陷周趨勢
應用Rails,團隊較快進入了可以通過資料進行質量分析的初級階段,當然還有很長的路要走,在從0到1的這個過程中,還是較多地體會到了敏捷開發的特性,也充分感受到了DRY理念。
總結
以上為半年左右時間內,外賣上海QA團隊在自動化工作上的一些實踐,總的來說,達到一定預期效果,整理這篇文章分享一些心得。所謂的主流與小眾並非絕對,主要從幾個方面衡量:
- 應用領域。Ruby因為效能問題,始終不太主流,但並不意味著它一無是處,用在測試領域,開發效率、DSL的友好性、語言的粘合性、使用者的學習低成本,都能發揮很大的優勢。
- 使用群體。不同的使用群體對於技能掌握的要求也是不同的,能達到同樣效果甚至超過預期則就可以選擇哪怕“小眾”的方案。
- 環境背景。其實有很多初創公司選擇Ruby作為初期的技術棧有一定的道理,而這與我們當初的情景有相似之處,實際效果也體現了語言的特性。
當然應用“小眾”技術,必然要面對不少挑戰:如何迅速培養能掌握相關技術的同學,與其他語言平臺的銜接問題,面對團隊的質疑等。尤其Ruby屬於易學難精的那種,從指令碼語言應用層次上升到動態語言設計層次還是需要一定的學習曲線的,也就是說對於使用者來說是簡單的,對於設計者的能力要求較高,就像流傳的Ruby程式設計師的進階過程就是魔法師的養成史。
正因為有特色的技術,才值得去研究和學習,就像它的設計者所說,目的就是為了讓開發人員覺得程式設計是件快樂的事情。做了這麼些年的測試,還能夠不停止寫程式碼的腳步,也是因為幾年前開始接觸Ruby。不論將來是否成為主流,它仍然是測試領域工具語言的不錯選擇,不管以後會出現什麼樣的技術,選型的標準也不會改變。技術的世界沒有主流與小眾,只有理解正確與否,應用得當與否。
寫在最後
美團外賣上海研發中心長期招聘前端、客戶端、後端、QA及資料、演算法相關的工程師,歡迎有興趣的同學傳送簡歷到huangzhuolin02@meituan.com。如果對我們團隊感興趣,可以關注我們的專欄。