前一陣子我們開始嘗試 UI 自動化測試,我們首先嘗試 Appium,藉此機會幫安裝過程做個記錄。
Appium 簡介
Appium 是一套開放原始碼的自動化測試框架,它支援 native / hybrid / mobile web apps。它走的是 Client-Server
架構,透過 REST API
與特定的 JSON protocol 進行溝通。
Server
Appium 說穿了就是個 HTTP Server,它使用 Node.js 開發,可以運行在本機端或遠端。它透過個別的 driver
跟不同的平台互動(iOS / Android / Windows / Mobile web / etc)。當它收到 client 請求,便會啟動 driver 跟模擬器或實機上的 app 溝通,取得特定資訊之後回傳給 client。
Client
Client 指的是 web drivers,目前官方提供 Ruby / Python / Java / JavaScript (Node.js) / PHP / C# (.Net) / RobotFramework
等程式語言開發的 web drivers。除此之外你可以選擇其他熟悉的語言開發,只要開發出來的東西符合 Selenium WebDriver(JSON Wire) Protocol + Mobile JSON Wire Protocol
即可。你高興的話,要使用 cURL
也可以。
常見測試流程
- 啟動 Server
- 建立 Client
- Client 跟 Server 要求建立特定的模擬器或實機的 session
- Client 送出指令(滑動 / 點擊 / 鍵盤輸入等等)
- Client 預期在一段時間內,app UI 會有預期的變化
如何安裝
以在 macOS Mojave 測試 iOS app 為例,說明如何安裝 Appium Client + Server。
Server
最簡單的方法就是下載 Appium Desktop,它裡頭包含了 Server 跟各種 Drivers。如果你有特殊需求,可以使用 npm 手動安裝。
Client
我們會透過 Homebrew 安裝需要的工具,如果還沒有安裝 Homebrew 的話,可以在 Terminal 輸入以下指令安裝。
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
你可以選擇喜歡的程式語言開發測試程式,這裡我使用 Python。以下指令會同時安裝 Python 3
跟 pip3
。
brew install python3
使用以下指令安裝 Appium Python Client 跟相關檔案。
pip3 install Appium-Python-Client
pip3 install -U pytest
測試 iOS app 還需要 Carthage,才能執行 WebDriverAgent
。
brew install carthage
最後,為了設定模擬器的各種 permissions,不讓它跳出確認對話框來煩我們,我們還需要安裝 AppleSimulatorUtils。
brew tap wix/brew
brew install wix/brew/applesimutils
如何打包測試 APP
這裡是以模擬器為例,如果要在實機測試,可以參考這個連結,如果要包其他平台,可以參考這個連結。首先我們要用模擬器的 SDK build 一個可以在模擬器運行的 app。
xcodebuild -showsdks
這個指令會列出所有安裝的 SDK,看起來像這樣,你的畫面可能跟我的會不太一樣。
➜ ~ xcodebuild -showsdks
iOS SDKs:
iOS 12.1 -sdk iphoneos12.1
iOS Simulator SDKs:
Simulator - iOS 12.1 -sdk iphonesimulator12.1
macOS SDKs:
macOS 10.14 -sdk macosx10.14
tvOS SDKs:
tvOS 12.1 -sdk appletvos12.1
tvOS Simulator SDKs:
Simulator - tvOS 12.1 -sdk appletvsimulator12.1
watchOS SDKs:
watchOS 5.1 -sdk watchos5.1
watchOS Simulator SDKs:
Simulator - watchOS 5.1 -sdk watchsimulator5.1
➜ ~
把模擬器的 SDK 記下來(這裡是 iphonesimulator12.1
),接下來切換到 app source code 的根目錄,執行以下指令包出 app。
xcodebuild -sdk iphonesimulator12.1 -workspace <Workspace File>.xcworkspace -scheme <Scheme Name> -derivedDataPath ~/Documents/DerivedData COMPILER_INDEX_STORE_ENABLE=NO | xcpretty
這裡有幾個要注意的參數:
-sdk
後面的參數就是剛才記下來的模擬器 SDK。-workspace
後面的參數要包含.xcworkspace
副檔名。-scheme
指定我們要的 scheme 設定。-derivedDataPaht
指定編譯過程產生的檔案要放在哪裡,為了比較好找到我們包出來的.app
檔,所以我指定到我的 Documents 底下。這樣.app
檔就會存在~/Documents/DerivedData/Build/Products/Debug-iphonesimulator/
底下。COMPILER_INDEX_STORE_ENABLE=NO
避免在 compile 的時候做 indexing,可縮短 build time。| xcpretty
是把產生的 log 導到 xcpretty,讓 log 以比較好讀的格式呈現。可以透過gen install xcpretty
安裝這個工具,這個設定不是必要的。
撰寫第一個測試
首先建立一個 app_config.py
,輸入以下內容,這個檔案指定了接下來會用到的一些設定。注意裡頭有些地方要改成你自己的設定(例如 app_path
的值)。
command_executor = "http://127.0.0.1:4723/wd/hub"
app_path = "/path/to/your/app/name.app"
simulator_caps_iphone_se = {
"platformVersion": "12.1",
"deviceName": "iPhone SE"
}
desired_capabilities = {
**simulator_caps_iphone_se,
"app": app_path,
"platformName": "iOS",
"automationName": "XCUITest",
"autoAcceptAlerts": False,
"sendKeyStrategy": "grouped",
"permissions": "{\"your.app.bundle.id\":{\"camera\":\"YES\",\"medialibrary\":\"YES\",\"microphone\":\"YES\",\"notifications\":\"YES\",\"photos\":\"YES\"}}"
}
我們建立一個 ui_testing.py
來撰寫測試程式碼。這裡要注意的是,我們使用 setUpClass()
跟 tearDownClass()
而不是一般 tutorial 用到的 setUp()
跟 tearDown()
,原因在於如果使用後者,每一個 test case 都會重新安裝 app 從頭來過。
還有一點要注意的是,每個 test case function 都是以 test
命名開頭,它會自動把這些 test case function 依名稱排序執行。但因為我們是 UI Test,每個測試之間有很強的順序性,所以我們在命名加上了序號,讓它依照我們想要的順序執行。
import unittest
from appium import webdriver
from time import sleep
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.ui import WebDriverWait
from app_config import *
class MyTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Remote(command_executor, desired_capabilities)
@classmethod
def tearDownClass(cls):
cls.driver.quit()
def test_1_FirstTest(self):
result = True
self.assertTrue(result)
def test_2_Second(self):
result = True
self.assertTrue(result)
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(MyTests)
unittest.TextTestRunner(verbosity=2).run(suite)
最後記得啟動你的 Appium Server,然後在 Terminal 輸入以下指令就可以開始我們的第一個測試了。
pytest ui_testing.py