Thousand Squared

Run Test On iOS

| Comments

如何在 iOS 上面寫測試

我們公司利用蠻常見的 test framework--GHUnit 來做 iOS app 測試,使用方便又簡單。

而且它文件寫的非常清楚,照著步驟走就可以安裝好 test project 囉!

<--------------------------->
2014/02/24
GHUnit 大更新啦,加上了好多更方便的指令!!

  1. 安裝 GHUnit gem

    $ gem install ghunit
    
  2. 產生 Tests target
    很簡單,只要一個指令就可以產生 test target 了,就不用跟以前一樣手動設定,超方便的!

    $ ghunit install -n ProjectName
    

    更方便的在後面,現在連新增 test 檔案都可以用 command line 的方式新增啦!

  3. 新增一個 測試的 .m 檔

    切換到專案目錄下,執行

    $ ghunit add -n ProjectName -f SampleTest
    

    <--------------------------->

Installing in iOS

當然,測試是一門高深的學問,要學也學不完呀,就分享在開發時最基本的測試方法與概念啦。

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:

View source on Github

假設我們要在 NSMutableArray 加上一個 reverse 功能

首先先建立一個 category: NSMutableArray + Util ,然後建立 method 的 interface

NSMutableArray+Util.m
@implementation NSMutableArray(Util)

- (void) nm_reverse
{
    
}

@end

之後在 Test project 下建立 NSMutableArray+UtilTests.m,用來測試所有 Util 裡面的的 method。
(如何建立 test 檔案:create and run test)

NSMutableArray+UtilTests.m
#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 啦

NSMutableArray+UtilTests.m
- (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 啦

NSMutableArray+Util.m
@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,也是可以測試的程式。

PrettyFormatterTests.m
- (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]], @"");
}

任何日期都可以測看看囉,不怕出現奇怪的時間啦。

View source on Github

小結

寫測試的的缺點,那就是開發時間會變長啦,如果是在接案公司的話,通常就不會做測試啦。

但如果是自己的產品,仔細的測試感覺就可以提高產品品質,減少出錯機率。而且寫測試也蠻好玩的,讓我在開發時變得比較嚴謹一些。有空的話,不妨寫一些測試吧。

Comments

comments powered by Disqus