本文由CocoaChina譯者小袋子翻譯
原文:How to Easily Switch Your App Delegate for Testing
測試驅動的開發最大好處是能夠有快速反饋(譯者:這是作者的另一篇文章,講述了測試驅動的好處,有興趣的可以看看)。所以,為了確保你的 TDD 效率,最好的方式就是盡可能快地獲得反饋。
但是很多 iOS 開發者會在測試的時候使用生產環境(譯者:應用開發中的不同階段,一般分為開發環境 development,處於產品開發階段;生產環境 production,即正式上線的環境,更詳細的請參照 Development, testing, acceptance and production)的 app delegate。這是一個影響效率的問題。
你的常規 app delegate 在用於測試時是否跟龜速一樣?
這是因為當你測試運行時,首先要啟動你的應用——而這個過程可能做了很多事情,大量耗時的操作。而這些耗時的操作在測試的時候並不是我們所需要的。
我們應該如何避免這個問題?
Apple 習慣將單元測試歸為兩類:應用測試和邏輯測試。這個區別是非常重要的,因為在以前,應用測試只能在設備上運行,除非你使用完全不同的第三方測試框架。
但是這個差異現在消失了,因為 Apple 允許我們在模擬器上運行應用測試。Apple 花了很多時間來更新文檔,直到在他們最新的Xcode測試才更新了這部分說明,Apple 現在稱之為 "app tests" 和 "library tests"。這就使事情簡化為你是開發一個應用還是一個庫。並且 Xcode 為你設置了一個測試用的 target ,這正是你所需要的。
如果我現在開發一個應用(或者一個需要運行應用的庫),我總是會運行應用測試,所以我停止去試圖區分這兩種類型的測試。但是由於 Xcode 是在一個運行的應用的上下文環境下執行應用測試,測試流程就變成這樣:
啟動模擬器
在模擬器中,啟動應用
將測試 bundle 注入運行的應用
運行測試
那麼我們怎麼才能加快這個流程呢?我們可以在第二步中做文章,讓應用盡可能快地啟動。
在開發環境下,啟動應用可能會關閉很多任務。Ole Begemann 在 Revisiting the App Launch Sequence on iOS中進行了詳細的解釋,但是根本上, UIApplicationMain()
最終會調用 app delegate去執行 application:didFinishLaunchingWithOptions:
。具體的流程一般取決於你的應用,但是很少會像下面這麼做:
創建 Core Data。
配置根視圖控制器
檢測網絡連通性
向服務器發送一個網絡請求去取回最近的配置,例如應該在根視圖中展示的東西。
因此在開始測試之前要做很多事情。難道不能更好地不被干擾,如果我們想要的只是運行我們的測試程序?
讓我們來解決這個問題,下面是具體方案。
讓我們改變我們的 main 函數,如下所示:
#import #import "AppDelegate.h" int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
我們現在想要去檢查是否我們在運行測試代碼。如果想要這麼做的話,我們想要去使用一個不同的 app delegate。我們可以這麼做:
最早的版本
#import #import "AppDelegate.h" #import "TestingAppDelegate.h" int main(int argc, char *argv[]) { @autoreleasepool { BOOL isTesting = NSClassFromString(@"XCTestCase") != Nil; Class appDelegateClass = isTesting ? [TestingAppDelegate class] : [AppDelegate class]; return UIApplicationMain(argc, argv, nil, NSStringFromClass(appDelegateClass)); } }
從根本上來說,如果 XCTestCase 鏈接好了,我們就會使用 TestingAppDelegate
。否則,我們退而使用生產環境的 app delegate。然後我們啟動應用時可以選擇我們想要的 app delegate。(注意:TestingAppDelegate 必須在生產環境的 target 中)
現在這些代碼已經實現了來回切換。上述部分的實現從根本上和我原先的文章一致。因為有一段時間,根據評論中的建議,我將代碼改為:
@autoreleasepool { Class appDelegateClass = NSClassFromString(@”XYZTestingAppDelegate”); if( appDelegateClass == nil ) { appDelegateClass = [DOAAppDelegate class]; } return UIApplicationMain(argc, argv, nil, NSStringFromClass(appDelegateClass)); }
但是在Xcode7上不能正常運行,所以我又改回原始版本。
如果你想在單元測試外部使用 XCTest 該怎麼辦,例如 UI 測試?為了取代為 XCTestCase 做的測試,你可以設置一個環境變量,通過 getenv 來測試。
這裡需要創建一個 TestingAppDelegate 類。正如下面代碼所示:
TestingAppDelegate.h
#import @interface TestingAppDelegate : UIResponder @property (nonatomic, strong) UIWindow *window; @end
TestingAppDelegate.m
#import "TestingAppDelegate.h" @implementation TestingAppDelegate @end
正如你所看到的那樣,不要做任何事。
(在早先的 iOS 版本中,我必須添加更多的代碼,導致 TestingAppDelegate 會創建一個 window,給這個 window 設置一個不做任何事情的根視圖,然後讓其可見。現在看來沒必要了。)
Bare bones for fast feedback
最重要的事情是我們已經從本質上減少了測試過程中啟動應用的步驟。盡管還有一些不必要的開銷,但是並不多。這是實現快速反饋過程中重要的一步,這樣我們就可以從 TDD 中獲得更多。
甚至當你開始一個新的項目,我推薦盡早使用這樣的方法,因為你真正的app delegate最終會變得日益龐大。讓我們在襁褓中阻止這種問題,然後保持快速的反饋。
另外一個好處是,通過完全控制哪部分該測試,什麼時候測試,我們現在可以編寫跟生產環境的app delegate完全不同的單元測試。這顯然是雙贏的。
本文中的所有譯文僅用於學習和交流目的,轉載請注明文章譯者、出處、和本文鏈接。
感謝博文視點為本期翻譯活動提供贊助