摘要:相對(duì)的測(cè)試方案,市面上已經(jīng)有非常多且成熟的級(jí)別的自動(dòng)化測(cè)試框架,卻鮮有針對(duì)提供的自動(dòng)化測(cè)試方案,原因是屬于為提供服務(wù)的插件。
作者:【友盟+】高級(jí)無(wú)線開發(fā)工程師 吳玉強(qiáng)、王飛
為了確保 SDK 線上運(yùn)行的穩(wěn)定性,我們需要在開發(fā)后進(jìn)行 SDK 測(cè)試,而為了提高測(cè)試效率,而且在拓展新項(xiàng)目的同時(shí)能兼顧已有項(xiàng)目的穩(wěn)定性,在有限的資源內(nèi)解放測(cè)試人員到更緊急的項(xiàng)目中來(lái),就需要一個(gè)自動(dòng)化工具來(lái)完成工作,【友盟+】首創(chuàng)自動(dòng)化工具,能夠自動(dòng)傳不同參數(shù)、抓取輸出數(shù)據(jù),并自動(dòng)驗(yàn)證數(shù)據(jù)準(zhǔn)確性,輸出結(jié)果,保障項(xiàng)目順利穩(wěn)定發(fā)布。
相對(duì) App 的測(cè)試方案,市面上已經(jīng)有非常多且成熟的 UI 級(jí)別的自動(dòng)化測(cè)試框架,卻鮮有針對(duì) SDK 提供的自動(dòng)化測(cè)試方案,原因是 SDK 屬于為 App 提供服務(wù)的“插件”。一個(gè) App 可接入一到多個(gè) SDK 在內(nèi),而在項(xiàng)目中模塊化是非常普遍的架構(gòu),所以 SDK 是針對(duì)細(xì)分功能提供服務(wù)的組件,有的提供數(shù)據(jù)服務(wù)、地圖服務(wù)或節(jié)省開發(fā)成本的組件等等,這只能 SDK 開發(fā)者根據(jù)功能自行完成測(cè)試。
本篇說(shuō)明的 SDK 測(cè)試方案是針對(duì)數(shù)據(jù)服務(wù)的 SDK 功能覆蓋,皆包含 SDK 的 API、網(wǎng)絡(luò)數(shù)據(jù)及緩存相關(guān)的邏輯測(cè)試,即非UI的純數(shù)據(jù)邏輯的覆蓋。
本篇是自動(dòng)化測(cè)試基礎(chǔ)上的延伸,相對(duì)安卓系統(tǒng)可以便利的通過(guò) adb 指令控制如App安裝、卸載、退出應(yīng)用等“系統(tǒng)”級(jí)操作,iOS 在控制 App 層面上只能通過(guò)一些間接的手段完成上面幾點(diǎn)需求,為了易于維護(hù),在控制器中以有限狀態(tài)機(jī)模式進(jìn)行了構(gòu)造,以便于后續(xù)增加更多的操作狀態(tài)和測(cè)試用例。
整個(gè)測(cè)試流程就如下面描述的有向圖,以 Pytest 驅(qū)動(dòng)客戶端執(zhí)行任務(wù),然后將客戶端輸出的請(qǐng)求數(shù)據(jù)進(jìn)行截取處理,而后驗(yàn)證是否通過(guò)測(cè)試用例。
2、Android端測(cè)試框架Android可以使用adb命令與app進(jìn)行數(shù)據(jù)上的通信,如發(fā)送廣播,啟動(dòng)Activity等。同時(shí)也可以使用shell命令對(duì)配置文件進(jìn)行修改,再進(jìn)行g(shù)radle編譯,實(shí)現(xiàn)對(duì)app級(jí)別參數(shù)的修改,從而完成不同參數(shù)對(duì)app程序影響的驗(yàn)證。
iOS由于系統(tǒng)特性,無(wú)法如安卓系統(tǒng)靈活運(yùn)用系統(tǒng)命令來(lái)操作 App 或 SDK,所以以一個(gè) Socket 連接 Server 端進(jìn)行通信。
另外在 iOS 系統(tǒng)上又可利用 Runtime 的特性,將傳輸?shù)淖址D(zhuǎn)化為 API 調(diào)用,這樣做的好處是將 Socket 模塊和 Runtime 解析模塊編入應(yīng)用中就無(wú)需再次打包,只需 Python 端編好代碼和測(cè)試 case,所有的功能調(diào)用都由兩端約定的協(xié)議解析執(zhí)行即可。
對(duì)于集成SDK的app,如果需要在App運(yùn)行時(shí),觸發(fā)一個(gè)行為,可以通過(guò)廣播來(lái)實(shí)現(xiàn)??梢愿鶕?jù)action name完成對(duì)行為類型的分類,根據(jù)caseid完成對(duì)行為的區(qū)分。如下圖所示:
根據(jù)上圖示例如下:
os.system( "adb shell am broadcast -a com.umeng.auto.track
--es param "" + str(es) + "" --ei caseId " + bytes(ei))
其中com.umeng.auto.track為廣播的action name用以區(qū)分類別
ei為一個(gè)int數(shù),相當(dāng)于圖中的caseid
es為參數(shù)內(nèi)容,參數(shù)的協(xié)議可以自由定義,建議使用json類型,方便對(duì)不同類型的數(shù)據(jù)進(jìn)行處理
這樣,我們只需要在廣播中以ei寫一個(gè)switch語(yǔ)句,執(zhí)行不同的行為,如果測(cè)試不同參數(shù)的效果,可以使用es傳遞內(nèi)容。
如果使用廣播,是沒辦法綁定生命周期,即如果SDK需要在Activity的onCreate()中進(jìn)行一些類初始化操作,是沒法進(jìn)行控制的。所以對(duì)于這種情況就需要使用adb命令中的啟動(dòng)Activity命令,基本流程與廣播類似,但是caseid的處理在onCreate()中:
根據(jù)上圖示例如下:
os.system("adb shell am start -n " + self.pkgname + "/." + activity + " --es param "" + str(es) + "" --ei caseId " + bytes(ei))
其中pkgname為包名,activity為activity的名字es為需要傳入的內(nèi)容,ei為一個(gè)int數(shù),即caseId。
與廣播方式類似,只是將switch放到了onCreate中,根據(jù)ei和es進(jìn)行相應(yīng)的操作。
以上說(shuō)的兩種方式幾乎可以涵蓋SDK測(cè)試的部分case,但是對(duì)于部分SDK,初始化需要在程序一啟動(dòng)的Application中執(zhí)行,這時(shí)上面的兩種方式顯然滿足不了需求。
這時(shí)有兩套方案可以應(yīng)對(duì)。如下圖所示:
二次編譯
如上圖所示,左邊的部分,我們可以通過(guò)修改Java文件完成對(duì)Appliction中內(nèi)容的修改,如在Application中會(huì)有一些靜態(tài)常量,使用python修改java文件中的常量,并重新運(yùn)行:
def changeConstant(self, source,des):
path = os.path.join(os.path.dirname(sys.path[0]), "autotestAndroid") gradle_path = os.path.join(path,"app","src","main","java","deep","autotest","utils","Constant.java") print "-----gradle_path----",gradle_path if os.path.exists(gradle_path): build_file = open(gradle_path, "r+") lines = build_file.readlines() for i in range(len(lines)): line = lines[i] if " "+source in line: arr = line.split("=") line = arr[0]+ "="+des+"; " lines[i] = line build_file = open(gradle_path, "w+") build_file.writelines(lines) p = buildprocess.CompileProcess(path) p.start() else:
print "nonono="+ gradle_path
使用這種方式的好處是:
? 可以直接修改Application中的常量,如AppKey等,不用管是否執(zhí)行了Application的onCreate()
? 不用考慮外設(shè)情況
? 同樣適配對(duì)AndroidManifest.xml的測(cè)試
缺點(diǎn)是:
? 需要綁定工程路徑
? 文件內(nèi)容類型較多,容易出錯(cuò),代碼不具備通用性,有一定的二次開發(fā)難度
? 需使用gradle重新編譯,如工程較大,耗時(shí)較長(zhǎng)
配置文件
除了上述方法,也可以在Application中讀取一個(gè)SD卡配置文件,根據(jù)配置文件的協(xié)議進(jìn)行對(duì)應(yīng)的操作。每次只需更改配置文件的內(nèi)容,并通過(guò)adb push放入SD卡指定路徑中,然后重啟App即可。
這樣做的好處是:
? 配置文件的協(xié)議可以隨意定義,更靈活
? 配置文件可以使用json格式,修改更簡(jiǎn)單
? 只需推到SD卡,耗時(shí)更少
? 不需要綁定工程路徑
缺點(diǎn)是:
? 只能在Application的onCreate之后進(jìn)行,局限性較大。
? 依賴外設(shè)SD卡
? AndroidManifest的測(cè)試無(wú)法使用。
如「iOS端測(cè)試框架」所見,此時(shí)進(jìn)行通信只有一個(gè)應(yīng)用,這個(gè)應(yīng)用就是我們用來(lái)測(cè)試 SDK 的 Demo,通過(guò)這個(gè)宿主我們可以觸發(fā) SDK 提供的任何 API,通過(guò) iOS runtime 我們可以觸發(fā) SDK 的類方法、實(shí)例方法甚至是私有 API,但這寫都只局限于一個(gè)應(yīng)用“沙盒”內(nèi),如上面說(shuō)到的安裝、卸載及 App 退出和切到后臺(tái)就無(wú)能為力了,所以我們引入了另一個(gè) Demo(Watch Demo),通過(guò)兩個(gè) Demo 的協(xié)同操作滿足“沙盒”之外的需求。
兩個(gè) App 互相喚醒和通信
如上面提到的,所有功能調(diào)用都基于約定的協(xié)議來(lái)執(zhí)行的,協(xié)議的設(shè)計(jì)也是不斷新增的測(cè)試需求改造的。
2、業(yè)務(wù)協(xié)議最初 Server 端與客戶端以測(cè)試用例的 case id 來(lái)區(qū)分需要觸發(fā)的事件,后來(lái) case id 所代表的含義太多,而且客戶端也是以運(yùn)行時(shí)不斷調(diào)用 Server 端發(fā)送指令的形式表現(xiàn)執(zhí)行的具體功能,所以轉(zhuǎn)為一條執(zhí)行序列更加靈活及方便擴(kuò)展。
一個(gè)測(cè)試用例可分為多條執(zhí)行序列,執(zhí)行序列內(nèi)的協(xié)議包含了需要進(jìn)行的方法調(diào)用或事件的處理。
以 Dplus 為例,如下數(shù)據(jù)包含了部分操作的執(zhí)行序列:
"operations": { "$umeng_cloudayc_op9": { "arguments": { "param": [ "$umeng_cloudayc_op*" ] }, "type": "class", "class": "DplusMobClick", "method": "track:" }, "$umeng_cloudayc_op5": { "arguments": { "param": [] }, "next": "$umeng_cloudayc_op9", "type": "class", "class": "DplusMobClick", "method": "clearSuperProperties" } }, "type": "invoke", "description": "401", "first": "$umeng_cloudayc_op5"
由于是針對(duì) SDK API 測(cè)試的協(xié)議,所以協(xié)議內(nèi)的格式以調(diào)用的類名、方法名及參數(shù)為主,再加上部分細(xì)節(jié)參數(shù)加以說(shuō)明,如 type 是 class 則調(diào)用類方法,是 instance 是示例方法。
需要注意的是,這個(gè)隊(duì)列的結(jié)構(gòu)是個(gè)字典,以標(biāo)識(shí)前綴 $umeng_cloudayc_op 作為一個(gè)子事件的 key,value 則是其執(zhí)行參數(shù)。而且可以看到在參數(shù) param 的 value 里也有和子事件的 key 類似的值,這里的設(shè)計(jì)也是為了滿足部分嵌套調(diào)用的需求。舉例來(lái)說(shuō),如此時(shí)需要通過(guò)一個(gè)接口驗(yàn)證之前緩存的數(shù)據(jù)是否發(fā)送正常,就要分三步,第一存儲(chǔ)數(shù)據(jù),第二將數(shù)據(jù)讀出,第三將第二步的結(jié)果作為參數(shù)傳入最后調(diào)用的接口即可,這樣既能滿足各種嵌套邏輯,又能實(shí)現(xiàn)遠(yuǎn)程構(gòu)造客戶端系統(tǒng)的實(shí)體對(duì)象作為參數(shù)進(jìn)行接口調(diào)用。
回到上面的字典的結(jié)構(gòu),實(shí)際上在之前的協(xié)議格式使用的是數(shù)組作為執(zhí)行序列的封裝格式,不過(guò)在實(shí)際應(yīng)用中無(wú)法滿足靈活的要求,就如上面所說(shuō)的組合的調(diào)用邏輯,有部分子事件是被動(dòng)調(diào)用的,通過(guò)在其他事件內(nèi)的參數(shù)檢測(cè)來(lái)觸發(fā)調(diào)用,如果是數(shù)組則無(wú)法控制這個(gè)執(zhí)行序列的依賴關(guān)系。采用字典后,增加啟動(dòng)字段,在后續(xù)關(guān)聯(lián)的子事件內(nèi),都會(huì)說(shuō)明下一個(gè)執(zhí)行的子事件,如果某個(gè)子事件是作為另外子事件的參數(shù),則不會(huì)有 next 字段,因?yàn)樗潜粍?dòng)觸發(fā)的,不在執(zhí)行隊(duì)列之內(nèi)。
在這個(gè)業(yè)務(wù)協(xié)議開發(fā)過(guò)程中,不斷的根據(jù)測(cè)試需求進(jìn)行改造、添加,從一開始的單一應(yīng)用調(diào)用接口,到后面的多應(yīng)用切換、前后臺(tái)切換以及應(yīng)用斷開和重連,需要多套控制流程,在具體實(shí)現(xiàn)時(shí),分散到了各個(gè)業(yè)務(wù)邏輯中,每增加一個(gè)控制都要兼容考慮是否會(huì)影響到其他模塊,而且作為一個(gè)自動(dòng)化測(cè)試“框架”,提前梳理好核心部分的流程會(huì)讓之后更易于開發(fā)和維護(hù),所以就引入了有限狀態(tài)機(jī)的概念進(jìn)行構(gòu)造。
3、有限狀態(tài)機(jī)有限狀態(tài)機(jī)(Finite-state machine)可用于模擬很多事物邏輯,顧名思義,它是一個(gè)有限的狀態(tài)的處理邏輯,有下面幾個(gè)特征:
狀態(tài)數(shù)是有限的
在當(dāng)前時(shí)刻只有一種狀態(tài)存在
一個(gè)狀態(tài)在滿足某個(gè)條件后會(huì)切換到另一狀態(tài)
而有限狀態(tài)機(jī)整體可以歸納為四個(gè)要素:現(xiàn)態(tài)、條件、動(dòng)作以及次態(tài)。
現(xiàn)態(tài)指當(dāng)前時(shí)刻所表現(xiàn)的狀態(tài)
條件又稱為事件,即當(dāng)前狀態(tài)在滿足這個(gè)條件后會(huì)觸發(fā)一個(gè)動(dòng)作,從而進(jìn)行狀態(tài)裝換
動(dòng)作即在現(xiàn)態(tài)滿足條件后需觸發(fā)的一系列操作,動(dòng)作完成后即狀態(tài)進(jìn)行遷移。動(dòng)作也可以忽略,在某些情況下,現(xiàn)態(tài)滿足條件后,也無(wú)需執(zhí)行任何動(dòng)作就切換到新的狀態(tài)。
次態(tài)是相對(duì)現(xiàn)態(tài)而言,表示了條件滿足后遷移的狀態(tài),次態(tài)也可以與現(xiàn)態(tài)相同。
根據(jù)業(yè)務(wù)邏輯的特性及復(fù)雜程度,合適的使用有限狀態(tài)機(jī),可以使得邏輯表達(dá)清晰、封裝及維護(hù)都很直觀和方便。當(dāng)一個(gè)業(yè)務(wù)包含的狀態(tài)越多,就越適合使用優(yōu)先狀態(tài)機(jī)進(jìn)行封裝處理。
有限狀態(tài)機(jī)應(yīng)用非常廣泛,如電子電路、編譯器及網(wǎng)絡(luò)協(xié)議 TCP 協(xié)議狀態(tài)機(jī)等
需要注意的是要區(qū)分“動(dòng)作”和“狀態(tài)”,如果將“動(dòng)作”也視為“狀態(tài)”會(huì)導(dǎo)致編寫狀態(tài)機(jī)時(shí)產(chǎn)生問(wèn)題。
4、有限狀態(tài)機(jī)應(yīng)用自動(dòng)化測(cè)試將業(yè)務(wù)邏輯應(yīng)用到有限狀態(tài)機(jī),前提是需要熟悉對(duì)應(yīng)的業(yè)務(wù),并將其中的狀態(tài)、動(dòng)作和條件等抽離出來(lái),然后再做進(jìn)一步的劃分和關(guān)聯(lián),構(gòu)造出一個(gè)完整的有向圖。
在自動(dòng)化測(cè)試中,有如下幾個(gè)關(guān)鍵詞:
啟動(dòng)測(cè)試、監(jiān)聽、主App連接、守護(hù)App連接、接口調(diào)用、進(jìn)入后臺(tái)、進(jìn)入前臺(tái)、應(yīng)用退出、崩潰、斷開連接、重連等。
在日常開發(fā)中,如果遇到上面的”事件”,可能就順其自然的開始寫判斷、寫調(diào)用,可能不自覺的就寫出了一個(gè)“有限狀態(tài)機(jī)”,不過(guò)不會(huì)那么嚴(yán)格的區(qū)分什么是動(dòng)作什么是狀態(tài),只要滿足最后的結(jié)果就能達(dá)成目的。
但現(xiàn)在我們有意識(shí)的利用有限狀態(tài)機(jī)進(jìn)行劃分,分離出狀態(tài)和動(dòng)作以及狀態(tài)遷移的條件??瓷厦娴年P(guān)鍵字,好像都是一個(gè)個(gè)“動(dòng)作”,仔細(xì)看“監(jiān)聽(中)”又可能是一個(gè)狀態(tài),但實(shí)際上我們還得需要結(jié)合業(yè)務(wù)的理解再抽象出一些狀態(tài),如“進(jìn)入后臺(tái)”,則是跳轉(zhuǎn)到了守護(hù) App,當(dāng)前是控制守護(hù) App 的狀態(tài);若是“進(jìn)入前臺(tái)”則守護(hù) App 跳轉(zhuǎn)到了“主App”,是控制主 App 的狀態(tài)。
如下圖就用剛才抽象出的關(guān)鍵詞構(gòu)造了一個(gè)簡(jiǎn)單的有限狀態(tài)機(jī):
按圖說(shuō)明:
1、如架構(gòu)圖描述的,需要主App和守護(hù)App同時(shí)連接才可執(zhí)行測(cè)試
2、在連接完成后,狀態(tài)直接遷移到等待測(cè)試指令的狀態(tài),沒有任何動(dòng)作
3、有些組合狀態(tài)可以合成一個(gè)狀態(tài),如運(yùn)行守護(hù)App狀態(tài)時(shí)可能主App斷開連接,也可能保持連接,所以區(qū)分為兩態(tài)分別管理。
4、當(dāng)自動(dòng)化測(cè)試框架啟動(dòng)后,除了監(jiān)聽兩個(gè)App同時(shí)連接,其他狀態(tài)都是在已有App連接完成的前提下進(jìn)行的,所以大部分時(shí)間是在執(zhí)行測(cè)試case調(diào)用及App切換的。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/8790.html
閱讀 2337·2021-11-24 10:18
閱讀 3422·2021-09-22 15:35
閱讀 3362·2021-09-13 10:37
閱讀 3782·2021-09-06 15:14
閱讀 2087·2021-09-06 15:02
閱讀 2235·2021-09-02 15:11
閱讀 561·2019-08-30 15:53
閱讀 3087·2019-08-29 16:15