iOS 的上架與內部測試管理

人生總有需要劃清界線的時候嘛

David Lin
David Lin
Aug 27, 2017 · 16 min read

前言

在前一家公司做開發的時候,後端有 staging 跟 production 兩個 server 可以使用,其中 staging 是拿來測(ㄨㄢˊ)試(ㄋㄨㄥˋ)的。我也常常在想,如果 iOS 也可以做到類似的事情就好了,而今天跟學弟在討論時,發現他已經完成這件事了!所以就來寫篇筆記以防以後要用到時忘記該如何切分環境。

開發時會遇到什麼變動的 key 呢?

之前在寫 iOS 時,會變動的 key 一般而言為 server 的 api access secret 跟 key、server 網址、分析工具(fabric, crashlytics, amplitude等等)。

有更特別需求如:facebook app id,開發與上架有不一樣的 app icon、name,甚至開發與上架兩個 app 的 bundle id 不一樣(內測、上架可以分成不一樣的 app)。

能分成不一樣的 app 在開發期間測試時會有很大的發揮空間,比如可以讓使用者在不移除原本程式的情況下,安裝測試版本 app。可以讓內部測試人員知道自己使用的 app 是否為測試版本。

怎麼實現呢?

一般來說,在你建立一個專案時,你的專案中會有兩個 configuration:debug 跟 release。對很多人來說,這兩個 configurations 沒有特別的差異,很多人寫好程式 archive 後就直接上架了,不太會去管這兩者的關係。

今天我們希望我們在開發時的版本為 dev 版,內部測試版為 beta 版,上架版本則沒有 suffix word。

同時有 3 個不同的 Sample app

建立新的 Configuration

首先,我們要先建立一個新的 Configuration,這個 Configuration 可以幫我們額外多處理一個不一樣的建置參數(configuration)。做法是 duplicate “Release” configuration,並且命名成 AppStore(名稱這邊取做 AppStore 是方便辨識用,你可以命名成你喜歡的名字)


不一樣的 App Name

剛剛有提到,我們希望在開發、內部測試、上架三個 app name 顯示不一樣的名稱,可以在 build settings 裡面的 product name 做修改。順帶一題,如果在修改 product name 時 double click 名稱修改時,應該會看到 $(TARGET_NAME) 這個變數,這個名稱就是左方 Targets 下面的 Target 的名稱。

同時安裝上架與內測用 App

iOS 在區分 app 是用 bundle id 來區分的,所以只要兩個 app bundle id 不一樣,這兩個 app 就可以同時存在。可以透過更改 build settings 中的 product bundle identifier 來實現。

這時候可以透過 edit scheme 來執行不一樣的 configuration。

同時存在 3 個 app

不一樣的 App Icon

當然如果可以讓內部測試人員可以直接透過看 icon 的不同而區分這是上架的或者內部測試用的 app 就再好不過了。要有不同的 app icon 也很簡單,首先先去 assests.xcassets 資料夾新增一個新的 appicon set,並且重新命名,再回到 build settings 改 icon set 即可。

新增 icon set
重新命名 icon set
改 build settings

注意 build version 與 short version

有時候你的專案不止單單一個 app,也許還會有 widget,這時候改動 build version 只會影響到某個 app,widget 並不會跟著改變 build version。這時候要在 Project 的 build settings 中自行定義兩個 user-defined 參數:

然後在每一個 version 跟 build 會跟著變動的地方,改成抓取 project 裡面定義的版本參數:

抓取 project version and build 自定義變數
build & version 跟著改變

在程式碼中區分 configuration

我們在開發時會連 staging 這個會有假資料的測試伺服器來測試新的 api 正不正常、或者有一些假的測試資料在這裡面。當然在要壓上架版本時,我們不希望還要重新改一次連線的網址、api secrect 等等,萬一沒有改好而且上架後才發布一切就晚了。

所以我們會希望能在寫 code 時自動區分現在是 debug 還是 production。首先我們先到 build settings 找到 other swift flag,並且新增一些 flag 以供我們判斷用。假設我們在 AppStore 這個 configuration 想要新增一個 APPSTORE 的 flag,可以直接 double click 他,並且按下左下方的 + 符號新增:

-D APPSTORE

注意一定要是 -D 開頭,如果第二次回來想要修改,會發現原本輸入的一行文字變成兩行了,這邊建議把兩個一起移除再重新修改。

加上 flag,這邊命名隨意,但一定要是以 -D 為開頭
注意開頭是 -D

接著我們來看 code 的部分:

先新增一個 swift file 叫做 ConfigurationMode.swift,定義一個 enum 來列舉所有可能出現的情況,接著在 Configuration class 裡面定義一個常數來存現在的 configuration。

如果你不需要用到這麼多情況,可以不需要用到 enum 直接使用 bool 即可。

//  Configuration.swiftfinal public class Configuration {
#if APPSTORE
public static let mode = ConfigurationMode.appStore
#elseif RELEASE
public static let mode = ConfigurationMode.beta
#else
public static let mode = ConfigurationMode.dev
#endif
}
public enum ConfigurationMode {
case appStore, dev, beta
}

當可以判斷目前的 configuration 後,就可以依據不一樣的情況,在自己定義的 key file 中給不一樣的 api key 囉!

// 你自行定義的 server configuration file.if Configuration.mode == .appStore {
print("App Store mode")
key = "appstore key"
} else if Configuration.mode == .beta {
print("Beta mode")
key = "beta key"
} else {
print("Dev mode")
key = "dev key"
}

不一樣的 Facebook App ID, Crashlytics API Key, Analytics API Key

有的時候想要區分測試與上架的使用者資料,我們需要兩個不一樣的 api key 來分別搜集資料。但像是 facebook 跟 crashlytics 都會在 info.plist 中新增一些資訊,甚至 crashlytics 會要我們在安裝他時,新增一段 run script,可以的話我們希望這些 api key 也會隨著 configuration 變化而改變,對我們而言是最方便的。

以下以 Crashlytics 為例:

crashlytics 在安裝時會需要我們在 build phase 中新增一段 run script,長得像:

"${PODS_ROOT}/Fabric/run" 37aee6e57ra2c9221d33ade845421f5433cd35e6 f15b918e91a881c17d8430a9192e2036d242742d31ad821f0a17a4f9c3c756c

仔細看後面的一串亂碼其實是由兩個資訊所組成的:

  1. 37aee7b57ea2c7221d33ade845421f5433cd35e6 為 api key
  2. f15b918e91a8781c17d8430a9192e2036d242742d31ad821f0a17a4f9c3c756c 為 build secret

現在我們希望這兩個資訊也隨著 configuration 改變,先到 build settings 的右上角找到 ➕ ,我們要新增兩個字定義的 key:CRASHLYTICS_API_KEY 與 CRASHLYTICS_BUILD_SECRET。

建立好後把相對應的 api key 跟 build secret 放進對應的 configure 中:


接著要修改 run script 的部分來取得隨著 configuration 變動的 key,用 ${CRASHLYTICS_API_KEY} 就可以拿到我們剛剛定義的值,build secret 一同:

最後要修改的是 info.plist 中的 api key,也是用上面的方法就可以取到變動的值,但要注意的是這次括號要改成 $() 才取得到:

以上就是 crashlytics 的改法,如果你的專案需要開源,且需要隱藏一些敏感的 api key,可以參考這篇:

Notification

因為我們三個 app 的 bundle id 都不一樣,所以我們需要在 apple developer center 中建立三個 app id。

在設定推播通知時要注意的是,在 beta 與 dev app 中,只需要開啟 development push notification service 即可,正式上架的 app 只需要開啟 production push notification service。

dev 或者 beta 只需要開啟 development push service 即可

手動設定 Provisioning file

provisioning file 要自行產生,給 xcode 自己產生會有問題。provisioning file 中也紀錄著這個 file 是給哪個 app 使用(根據 bundle id),給哪些裝置使用(新增裝置也從這裡下手),所以 xcode 會無法根據 bundle id 幫你產生這些檔案,因為他抓不到 beta or AppStore 的 bundle id。

因此 auto signing 要關閉,這邊要手動來。

首先我們要先建立兩個 development provisioning file 給 dev 跟 beta 使用:

建立後記得要下載下來。

接著建立 production 用 profile:

再來把三個 provisioning file 丟進專案資料夾中:

回到專案中根據 configuration 挑選 provisioning profile:

最後長這樣:


建立 AppStore Scheme

前面這些設定做好後,我們就可以準備上架啦!我們現在有兩個情況要處理:內部測試 上架AppStore,內部測試要使用 beta 的 configuration 來 archive,AppStore 則是要用 AppStore Configuration 來壓。而 archive 按鈕只有一個,我們要怎麼讓 xcode 幫我們壓出兩種不一樣的 app 呢?

很簡單,我們只需要改 scheme 即可。我們會壓出內測與上架的 app,dev app 則是我們在 local 端測試用的所以先不理他。先看看預設的 scheme:

右邊圈起來的是我們可以選擇在 cmd+R 執行時,執行的 configuration。但在 archive 時會執行的是左邊紅色圈圈中顯示的 Release configuration,所以在這個 scheme archive 出來的是 release 也是 beta 的 app。

現在我們要新增一個 scheme 叫做 Sample AppStore:

Target 不要選錯了,不要選到 tests 或者 pods

接著我們要修改 Sample AppStore Scheme 中 archive 會執行的 configuration:

這樣我們要上架就可以選 Sample AppStore Scheme 壓出上架版本,選 Sample Scheme 壓出內部測試版本了。


使用 Fastlane 幫你處理上架與發佈內部測試的事

使用 Fastlane 一段時間了,Fastlane 主要就是幫你簡化 app 上架前複雜的工序,他可以幫你在 itunes connect 上建立 app、搞定憑證問題、壓 app、跑測試、截圖上傳 itunes connect 等等,幾乎你在發 app 會遇到的問題他都幫忙解決了。

主要拿 fastlane 來幫忙在發佈內部測試時壓好 app 並且上傳到 fabric beta 以供內部測試人員測試,上架 app 時自動壓好 app 並且上傳 itunesconnect

以下不細說 fastlane setup 方法,官方寫得很清楚了。但注意在自動建立 itunes connect app id 時不要給錯 bundle id 了。

打開 fastfile,如果是 beta 的話就是:

lane :beta do
gym(scheme: "Sample", export_method: 'development')
emails = ["johnnyappleseed@me.com"]
crashlytics(
emails: emails, # 測試人員 email
api_token: "your_api_key",
build_secret: "your_build_secret",
notifications: true # email 通知測試者
)
end

根據 gym usage 顯示,沒有要上架的 app 的 export_method 要特別設定過,沒有 code sign 直接 gym 會炸(這邊不用 code sign)。

而 gym 完後可以透過 crashlytics 發佈測試版,你也可以透過其他的 beta tool 發佈(例如 testflight, HockeyApp),email 為你要通知的對象,notifications 會寄發 email 給對方。

上架 AppStore:

lane :appstore do
ENV["FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD"] = "如果你使用two-way 驗證就需要這個密碼"
cert
sigh
gym(scheme: "Sample AppStore") # scheme 不要選錯
deliver(force: true)
end

就是這麼簡單~但不得不說,關於 fastlane 可以使用的參數相關的文件非常少或者只挑重點寫,都只能碰碰運氣或者找關鍵字直接看他們 code 才能挖到一點東西。


結語

雖然這些設定很麻煩,但想想看設定完之後可以一行指令幫你上傳到你想要的地方,是不是節省掉很多人為操作上的問題?這次跟學弟合作開發學到很多以前沒有想過的東西,也感謝他先踩過滿滿的雷,才有今天這篇文章的出生,我只是總結並且把設定過程從頭到尾走過一次,以供以後需要用到時參考。

最後的最後,請 follow 他的 github:

更多請參考:

)
Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade