「小眾」之美——Ruby在QA自動化中的應用
前言
關於測試領域的自動化,已有很多的文章做過介紹,「黑科技」也比比皆是,如通過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,主要有以下特點:
1. 測試數據處理獨立
- 預先生成測試所需的最終數據,區分單介面測試數據(單介面數據驅動測試)與鏈路測試數據
- 通過命令行形式的語句解決了參數的多層嵌套及動態數據生成的問題
- Excel中維護測試數據,最終轉化為YML或存入DB,折中解決了JSON形式的數據難維護問題
2. 學習成本低
- 框架提供生成通用結構代碼的功能,使測試人員更關注於業務邏輯處理
- DSL的書寫風格,即便沒有Ruby的語言基礎,也可以較快掌握基本的介面測試用例編寫
3. 擴展性
- 支持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: E000001requestId: 1938670097orderInfo: orderId: 6778043386 count: 2 name: testgoodspayInfo:- transactionId: 510455433082284 payTime: 2017-04-04 13:03:34 payType: BOC- transactionId: 167338836018587 payTime: 2017-04-04 13:03:34 payType: WalletcreateTime: 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 endend
Excel作為Case的維護形式,缺點是Case較多情況下頻繁讀取比較影響時間。在這種情況下,考慮到把數據序列化到YML中,啟動執行時介面測試類自動與測試數據進行綁定。在Case中可以直接使用形如
DemoTest.request[1]的請求數據,提高了速度,結構上也清晰了不少。介面測試類文件(HTTP介面調用為例)生成的模板如下,修改對應的介面信息即可,支持DB驗證(代碼塊p這部分是目前唯一需要寫Ruby代碼的地方,當然這是非必需項)。
require apicasebaseclass 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 pend
TestCase文件如下,原則上無需修改,只需要在測試數據的Excel中編寫匹配規則及預期輸出,基本上實現了單個介面無編碼的數據驅動測試。
require Preview_validateRSpec.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]}") endendRSpec.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_objectsBefore{$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"]}")} endend......
support/pages為Page層,實現了每個頁面下的操作,目前把它實現為Kernel中的方法,採用中文命名,便於閱讀使用。
module Kernel def 點擊我 Elements.微信我.click end def 點擊收藏按鈕 Elements.微信收藏.click end def 點擊收藏項 Elements.微信收藏鏈接.click end def 點擊收藏中的美團外賣鏈接 Elements.微信收藏鏈接URL.click endend
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團隊在自動化工作上的一些實踐,總的來說,達到一定預期效果,整理這篇文章分享一些心得。所謂的主流與小眾並非絕對,主要從幾個方面衡量:
1. 應用領域。Ruby因為性能問題,始終不太主流,但並不意味著它一無是處,用在測試領域,開發效率、DSL的友好性、語言的粘合性、使用者的學習低成本,都能發揮很大的優勢。
2. 使用群體。不同的使用群體對於技能掌握的要求也是不同的,能達到同樣效果甚至超過預期則就可以選擇哪怕「小眾」的方案。
3. 環境背景。其實有很多初創公司選擇Ruby作為初期的技術棧有一定的道理,而這與我們當初的情景有相似之處,實際效果也體現了語言的特性。
當然應用「小眾」技術,必然要面對不少挑戰:如何迅速培養能掌握相關技術的同學,與其他語言平台的銜接問題,面對團隊的質疑等。尤其Ruby屬於易學難精的那種,從腳本語言應用層次上升到動態語言設計層次還是需要一定的學習曲線的,也就是說對於使用者來說是簡單的,對於設計者的能力要求較高,就像流傳的Ruby程序員的進階過程就是魔法師的養成史。
正因為有特色的技術,才值得去研究和學習,就像它的設計者所說,目的就是為了讓開發人員覺得編程是件快樂的事情。做了這麼些年的測試,還能夠不停止寫代碼的腳步,也是因為幾年前開始接觸Ruby。不論將來是否成為主流,它仍然是測試領域工具語言的不錯選擇,不管以後會出現什麼樣的技術,選型的標準也不會改變。技術的世界沒有主流與小眾,只有理解正確與否,應用得當與否。
招聘信息
最後插播一條廣告,美團外賣上海研發中心長期招聘前端、客戶端、後端、QA及數據、演算法相關的工程師,歡迎有興趣的同學發送簡歷到huangzhuolin02@meituan.com。
也許你還想看:
Lego:美團點評介面自動化測試實踐
基於 Appium 的 Android UI 自動化測試
移動App兼容性測試工具Spider
http://weixin.qq.com/r/9HVSSg3EOFBHrUkp9yDm (二維碼自動識別)
推薦閱讀:
※王源受過什麼不公平的待遇你知道嗎?
※大數據應用+搜索引擎:重構信息傳播方式
※做好海外運營需要掌握哪些核心數據?
※APP同步抓取明星動態 新浪微博索賠200萬
※虎牙直播特色是什麼?