前一陣子我們開始嘗試 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 也可以。

常見測試流程

  1. 啟動 Server
  2. 建立 Client
  3. Client 跟 Server 要求建立特定的模擬器或實機的 session
  4. Client 送出指令(滑動 / 點擊 / 鍵盤輸入等等)
  5. 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 3pip3

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