由於業務需求和準備畢設,最近開始研究自動化測試的內容。因爲同時要作 iOS、安卓和 Web 測試,咱們最終選擇了 Appium 這個開源工具並基於它作一些封裝,從而可以使用一套公共 API 完成移動端的雙端測試。本文主要會基於一些開源代碼和我的實踐,對 iOS 端的自動化測試原理作一個簡單介紹,Android 略有區別但也大體同理。html
其實文章沒有很長,也沒有太多技術含量,驅使我寫這篇文章的主要緣由是 Google 上能搜到的絕大多數博客都是錯的,大可能是根據一篇老舊過期的文章抄抄改改。因此真的很想問問這些文章的做者,你真的搞懂 Appium 的原理麼?對這一些錯誤的知識有可能搞懂麼?本身不先搞懂,怎麼能昧着良心寫進博客裏?java
我假設讀者徹底沒有了解過自動化測試以及相關的概念,那麼首先就要搞明白 Appium 是什麼,大概由幾個步驟組成,接下來纔是對每一個部分的深刻了解。ios
簡單來講,Appium 是一個測試工具,能夠進行 iOS、Android 和 Web 測試,同時還容許使用多種語言來編寫測試用例。那麼問題就變成了爲何 Appium 支持多種語言來寫測試用例,以及這些測試用例是如何運行在具體的平臺(好比 iOS )上的。爲了回答這個問題,咱們須要把 Appium 分紅三個部分來看,分別是 Appium 客戶端、Appium 服務端和設備端。既然是自動化測試,那麼就先從設備端提及。git
若是你按照官網的教程成功的運行了 iOS 真機測試,你會看到手機上多了一個名爲 WebDriverAgentRunner 的應用,之後簡稱 WDA,這個應用的做用就是對你的目標 App 進行測試。程序員
好吧,是否是以爲事情有點神奇了?安裝了一個別人的 app,竟然能喚起你本身的應用,還能執行測試,是否是存在什麼黑魔法?github
首先須要介紹一下蘋果的 UI 自動化測試框架,在 Xcode 7 之前使用了UI Automation
框架,利用 JS 腳本去作應用測試。而在 Xcode 7 中蘋果提供了新的框架 UI Testing
,在 Xcode 8 中乾脆直接移除了對 UI Automation
的支持。因此毫無疑問,在 iOS 9 或者更高的系統版本中,Appium 也是利用了 UI Testing
框架來作測試而不是UI Automation
。web
不少程序員應對 UI Testing
框架並不陌生,在新建項目的時候就有機會勾選上這個選項,或者後期經過 Add target
的方式補上。默認狀況下,一個測試用例就是一個 .m
文件,模板代碼以下: swift
#import <XCTest/XCTest.h>
@interface Test : XCTestCase
@end
@implementation Test
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
self.continueAfterFailure = NO;
// UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
[[[XCUIApplication alloc] init] launch];
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
- (void)testExample {
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
@end複製代碼
能夠看到一共只有三個方法, setUp
方法中主要作一些測試前的準備,好比這裏的 [[[XCUIApplication alloc] init] launch];
就建立了一個被測試應用的實例並喚起它。tearDown
方法是測試結束後的清理工做。服務器
全部的測試函數都必須以 test
開頭,好比這裏的 - (void)testExample
。好吧,不得不認可 OC 這們語言仍是有缺陷的,缺乏了 Annotation 之後就只能用變量名來作標記,這種業務對應的 Java 表示應該是:網絡
@test
public void example() {
// do some test
}複製代碼
言歸正傳,有了這樣的測試代碼後,只要 Command + U
就能夠運行測試。不過這仍是沒有解決以前的疑惑,爲何 Appium 能夠用一個第三方 app 喚起待測試的應用並進行調試?固然,這個問題等價於,上面代碼中的 [[XCUIApplication alloc] init]
到底會建立一個什麼樣的 app 實例?它怎麼知道這個對象的 launch
方法會打開手機上的哪一個 app?
這個問題彷佛沒有搜到比較明確的答案,不過通過實踐分析之後發現,XCUIApplication
類存在一個私有方法,能夠傳入目標應用的 BundleID:
[[XCUIApplication alloc] initPrivateWithPath:nil bundleID:@"com.bestswifte.targetapp"];複製代碼
咱們知道手機上一個 BundleID 惟一對應了一個應用,後裝的應用會替換掉以前相同 ID 的應用,因此經過 BundleID 老是能夠正確的喚起待測試應用。惟一要注意的是,爲了順利經過編譯器的語法檢測,咱們在調用私有方法以前須要先構造一份 XCUIApplication
的頭文件,聲明一下將要調用的私有方法。
當咱們用這個私有的初始化方法替換掉默認的 init
方法後,就能夠正常喚起待測試應用了,不過你會發現被測試的應用剛一打開就會退出,這是由於咱們的測試代碼內容爲空,因此很快就會進入到銷燬流程。
解決問題也很簡單,咱們能夠在 testExample
裏面跑一個死循環,模擬 Runloop 的操做。只不過此次不是監聽用戶事件,而是監聽某個 TCP 端口,等待網絡傳輸過來的消息。
咱們以 Facebook 開源的 WDA 爲例,看看它的 FBScreenshotCommands.m
文件:
#import "FBScreenshotCommands.h"
#import "XCUIDevice+FBHelpers.h"
@implementation FBScreenshotCommands
#pragma mark - <FBCommandHandler>
+ (NSArray *)routes
{
return
@[
[[FBRoute GET:@"/screenshot"].withoutSession respondWithTarget:self action:@selector(handleGetScreenshot:)],
[[FBRoute GET:@"/screenshot"] respondWithTarget:self action:@selector(handleGetScreenshot:)],
];
}
#pragma mark - Commands
+ (id<FBResponsePayload>)handleGetScreenshot:(FBRouteRequest *)request
{
NSString *screenshot = [[XCUIDevice sharedDevice].fb_screenshot base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
return FBResponseWithObject(screenshot);
}
@end複製代碼
這裏首先會註冊一個 /screenshot
的路由,而且制定處理函數爲 handleGetScreenshot
,而後函數內部調用 XCUIDevice
的截圖方法。
因此分析到這裏,WDA 的套路就很清晰了,它能根據被測試應用的 BundleID 將它喚起,而後本身進入死循環保證測試用例一直不退出。此時等待服務器傳來 URL 數據,而後解析 URL,分發到對應模塊,各個模塊根據 URL 指令執行對應的測試操做,最終再把測試結果返回。
簡單來講,Appium 服務端是一個 Node.js 應用,這個應用跑在電腦上,用於和 WDA 進行通訊。剛剛咱們看到了截圖命令的 URL 是 /screenshot
,能夠想見還有別的相似的測試操做,因此 WDA 有必要和 Appium 服務端約定一套通訊協議。考慮到 Appium 還支持 Android 測試,因此在安卓手機上也有相似的東西須要和 Appium 服務端進行交互。這樣一來,約定一套通用的協議就顯得很是重要。
Appium 採用的是 WebDriver 協議。在 w3.org 上有一個對該協議的詳細描述,而 Selenim 的官網 也介紹了 WebDriver 協議。目前我尚不知道這兩處介紹的關係,是互爲補充 or 兩套規範,但能夠確定的是下面這段話的介紹:
WebDriver’s goal is to supply a well-designed object-oriented API that provides improved support for modern advanced web-app testing problems.
因此簡單的把 WebDriver 理解成一套通用的測試協議便可。
Appium 客戶端就是指咱們寫的那些測試代碼了。Appium 支持多種測試語言的根本緣由在於,WebDriver 協議爲各類主流語言提供了一個第三方庫,可以方便的把測試腳本轉化成符合 WebDriver 規範的 URL。好比 www.w3.org/TR/webdrive… 就規定了包括截圖、尋找元素、點擊元素等操做對應的 URL 格式。
咱們的項目目前使用 Java 語言來編寫測試腳本,這樣的好處是 Android 工程師就能夠承擔起維護和編寫 Android、iOS 兩個平臺下測試代碼的重任了。
其實 Appium 的原理很是簡單,一句話就能歸納:
提供各個語言的第三方庫,將測試腳本轉化成 WebDriver 協議下的 URL,經過 Node 服務發送到各個平臺上的代理工具,代理工具在運行過程當中不斷接收 URL,根據 WebDriver 協議解析出要執行的操做,而後調用各個平臺上的原生測試框架完成測試,再將測試結果返回給 Node 服務器。
最後再友情提醒一句:
iOS 上的真機測試環境很難配置,若是一天搞不定,不要氣餒,多試幾回,也許明天就行了
學習的過程當中走了很多彎路,若是你看到下面這兩篇文章,建議馬上按下 Command + w
,不要浪費時間去學習錯誤知識了: