如何在 iOS 上面寫測試
我們公司利用蠻常見的 test framework--GHUnit 來做 iOS app 測試,使用方便又簡單。
而且它文件寫的非常清楚,照著步驟走就可以安裝好 test project 囉!
<--------------------------->
2014/02/24
GHUnit 大更新啦,加上了好多更方便的指令!!
-
安裝 GHUnit gem
$ gem install ghunit
-
產生 Tests target
很簡單,只要一個指令就可以產生 test target 了,就不用跟以前一樣手動設定,超方便的!$ ghunit install -n ProjectName
更方便的在後面,現在連新增 test 檔案都可以用 command line 的方式新增啦!
-
新增一個 測試的 .m 檔
切換到專案目錄下,執行$ ghunit add -n ProjectName -f SampleTest
<--------------------------->
當然,測試是一門高深的學問,要學也學不完呀,就分享在開發時最基本的測試方法與概念啦。
Why test?
以前我也不會寫測試,一個功能寫好以後,重新 compile project,run 整個程式,自己多帶一些參數測測看就好啦,run 到有問題時就知道啦...0rz
後來跟著 Senior 才慢慢學到為什麼要寫測試,如何寫測試。身為一個專業的開發者,熟練寫測試可以可以減少 bug 發生,也可以讓自己的想法更加清楚。
通常要寫一個新的 method(or function),會先想好這個東西要幹嘛,然後定義它帶入的參數,回傳的值等等。在以前,總是要等到程式寫好了,run 在實際的情況下,我才會知道這個 method (or function) 有沒有寫對。如果此 project 又特別大,compile 要 3 分鐘,那就會很浪費時間啦。
自從學到怎麼寫測試後,開發流程就稍微改變了,一樣會先定義好一個 method 的 interface。接著開始寫 test case,盡量把所有的測試情況都寫出來。寫完 test case 後才開始 implement method。之後就等完成 method 內容,直接 run test,就可以知道有沒有寫錯啦!
Example:
假設我們要在 NSMutableArray 加上一個 reverse 功能
首先先建立一個 category: NSMutableArray + Util ,然後建立 method 的 interface
@implementation NSMutableArray(Util)
- (void) nm_reverse
{
}
@end
之後在 Test project 下建立 NSMutableArray+UtilTests.m,用來測試所有 Util 裡面的的 method。
(如何建立 test 檔案:create and run test)
#import <GHUnitIOS/GHUnit.h>
#import "NSMutableArray+Util.h"
@interface NSMutableArray_Util : GHTestCase
{}
@end
@implementation NSMutableArray_Util
- (BOOL) shouldRunOnMainThread
{
// By default NO, but if you have a UI test or test dependent on running on the main thread return YES.
// Also an async test that calls back on the main thread, you'll probably want to return YES.
return NO;
}
- (void) setUpClass
{
// Run at start of all tests in the class
}
- (void) tearDownClass
{
// Run at end of all tests in the class
}
- (void) setUp
{
// Run before each test method
}
- (void) tearDown
{
// Run after each test method
}
- (void) testReverse
{
}
@end
有了這個測試的.m檔之後,就可以來寫 test case 啦
- (void) testReverse
{
NSMutableArray* mutableArray = [NSMutableArray array];
[mutableArray nm_reverse];
GHAssertEquals((NSUInteger) 0, mutableArray.count, @"");
[mutableArray addObject:@"1"];
[mutableArray nm_reverse];
GHAssertEquals((NSUInteger) 1, mutableArray.count, @"");
GHAssertEqualStrings(@"1", mutableArray[0], @"");
[mutableArray addObject:@"2"];
[mutableArray addObject:@"3"];
[mutableArray addObject:@"4"];
[mutableArray nm_reverse];
GHAssertEquals((NSUInteger) 4, mutableArray.count, @"");
GHAssertEqualStrings(@"4", mutableArray[0], @"");
GHAssertEqualStrings(@"3", mutableArray[1], @"");
GHAssertEqualStrings(@"1", mutableArray[3], @"");
[mutableArray removeAllObjects];
for (int i = 0; i < 100; i++)
{
[mutableArray addObject:[NSString stringWithFormat:@"%i", i]];
}
[mutableArray nm_reverse];
for (int i = 0; i < 100; i++)
{
NSString* const expect = [NSString stringWithFormat:@"%i", i];
GHAssertEqualStrings(expect, [mutableArray objectAtIndex:99 - i], @"");
}
}
@end
主要就是測一些極端的現象,或是容易出錯的case,像是空陣列、只有一個 element 等等。然後測試 reverse 之後的值對不對。
寫完後 run test,確保 test case 語法沒有錯誤。
最後就會得到測試失敗的訊息。(因為還沒有implement method)
之後就可以來 implement method 啦
@implementation NSMutableArray(Util)
- (void) nm_reverse
{
const NSUInteger arrayCount = [self count];
if (arrayCount == 0 || arrayCount == 1)
{
return;
}
NSInteger i = 0;
NSInteger j = arrayCount - 1;
while (i < j)
{
[self exchangeObjectAtIndex:i
withObjectAtIndex:j];
i++;
j--;
}
}
@end
確定完成後就可以來測試自己有沒有寫錯啦! Run test !
成功!
Test what?
基本上邏輯的東西一定要測,還有DB存取(像dao),加解密等等,我們能測都有測。UI也是可以測,但那又是另外的測試方法,我們就沒有做啦。
撈db時有時會遇到 async 的時候,沒問題,也是可以測,寫法有些不同,可以看文件裡怎麼寫。
而前幾篇的 PrettyFormatter,也是可以測試的程式。
- (void) testFacebookFormatter
{
NSDate* now = [NSDate date];
GHAssertEqualStrings(@"Just now", [PrettyFormatter facebookFormat:[now dateByAddingTimeInterval:0]], @"");
GHAssertEqualStrings(@"Just now", [PrettyFormatter facebookFormat:[now dateByAddingTimeInterval:2]], @"");
GHAssertEqualStrings(@"3 seconds ago", [PrettyFormatter facebookFormat:[now dateByAddingTimeInterval:-3]], @"");
GHAssertEqualStrings(@"30 seconds ago", [PrettyFormatter facebookFormat:[now dateByAddingTimeInterval:-30]], @"");
GHAssertEqualStrings(@"1 minute ago", [PrettyFormatter facebookFormat:[now dateByAddingTimeInterval:-60 * 1]], @"");
GHAssertEqualStrings(@"5 minutes ago", [PrettyFormatter facebookFormat:[now dateByAddingTimeInterval:-60 * 5]], @"");
GHAssertEqualStrings(@"55 minutes ago", [PrettyFormatter facebookFormat:[now dateByAddingTimeInterval:-60 * 55]], @"");
GHAssertEqualStrings(@"1 hour ago", [PrettyFormatter facebookFormat:[now dateByAddingTimeInterval:-3600 * 1]], @"");
GHAssertEqualStrings(@"23 hours ago", [PrettyFormatter facebookFormat:[now dateByAddingTimeInterval:-3600 * 23]], @"");
GHAssertEqualStrings(@"Yesterday", [PrettyFormatter facebookFormat:[now dateByAddingTimeInterval:-3600 * 24]], @"");
NSDateFormatter* formatter = [[[NSDateFormatter alloc] init] autorelease];
[formatter setDateFormat:@"EEEE 'at' HH:mm"];
NSTimeInterval fiveDaysAgo = -3600 * 24 * 5;
GHAssertEqualStrings([formatter stringFromDate:[NSDate dateWithTimeIntervalSinceNow:fiveDaysAgo]],
[PrettyFormatter facebookFormat:[now dateByAddingTimeInterval:fiveDaysAgo]], @"");
GHAssertEqualStrings(@"1 year ago", [PrettyFormatter facebookFormat:[now dateByAddingTimeInterval:-3600 * 24 * 365]], @"");
GHAssertEqualStrings(@"10 years ago", [PrettyFormatter facebookFormat:[now dateByAddingTimeInterval:-3600 * 24 * 365 * 11]], @"");
}
任何日期都可以測看看囉,不怕出現奇怪的時間啦。
小結
寫測試的的缺點,那就是開發時間會變長啦,如果是在接案公司的話,通常就不會做測試啦。
但如果是自己的產品,仔細的測試感覺就可以提高產品品質,減少出錯機率。而且寫測試也蠻好玩的,讓我在開發時變得比較嚴謹一些。有空的話,不妨寫一些測試吧。