1. 程式人生 > >Kiwi,BDD行為測試框架--iOS攻城獅進階必備技能

Kiwi,BDD行為測試框架--iOS攻城獅進階必備技能

簡介

Kiwi 是一個適用於iOS開發的行為驅動測試框架,旨在提供一個足夠簡單易用的BDD庫.

使用Cocopods 安裝

target :AmazingAppTests, :exclusive => true do
  pod 'Kiwi'
end

把 AmazingAppTests 改為你自己的工程中的Tests target的名字,比如我的是 iOS122Tests,然後更新即可:

pod update --verbose --no-repo-update

為了快速測試Kiwi是否安裝成功,你可以用下面的程式碼替換到你的 Tests目錄下已有的檔案中的預設內容,然後點選Xcode導航欄 Product->Test(或者使用快捷鍵 cmd + u),此時如果提示你 Test Failed,點選錯誤提示,會在左側第四導航欄看到類似下面的錯誤:

Assertions: 'Math, is pretty cool' [FAILED], expected subject to equal (KWValue) 43, got (KWValue) 42
File: MathSpec.m:9

如果不能看到上述錯誤資訊,說明你的工程配置可能有問題,可以參考這裡詳細微調下: Getting Started with Kiwi 2.0

規則

Kiwi的規則由以下元素組成

  • #import "Kiwi.h" 匯入Kiwi庫.這應該在規則的檔案開始處最先匯入.
  • SPEC_BEGIN(ClassName)SPEC_END 巨集,用於標記 KWSpec 類的開始和結束,以及測試用例的分組宣告.
  • registerMatchers(aNamespacePrefix) 註冊所有使用指定名稱空間字首的匹配器.除了Kiwi預設的匹配器,這些匹配器也可以在當前規則中使用.
  • describe(aString, aBlock) 開啟一個上下文環境,可包含測試用例或巢狀其他的上下文環境.
  • 為了使一個block中使用的變數真正被改變,它需要在定義時使用 __block 修飾符.
  • beforeAll(aBlock) 在所有內嵌上下文或當前上下文的`itblock執行之前執行一次.
  • afterAll(aBlock) 在所有內嵌上下文或當前上下文的`itblock執行之後執行一次.
  • beforeEach(aBlock)
    在所有包含的上下文環境的 itblock執行之前,均各執行一次.用於初始化指定上下文環境的程式碼,應該放在這裡.
  • afterEach(aBlock) 在所有包含的上下文環境的 itblock執行之後,均各執行一次.
  • it(aString, aBlock) 宣告一個測試用例.這裡描述了對物件或行為的期望.
  • specify(aBlock) 宣告一個沒有描述的測試用例.這個常用於簡單的期望.
  • pending(aString, aBlock) 可用於標記尚未完成的功能或用例,僅會使Xcode輸出一個黃色警告.(有點TODO的趕腳)
  • let(subject, aBlock) 宣告一個本地工具變數,這個變數會在規則內所有上下文的每個 itblock執行前,重新初始化一次.

示例.

#import "Kiwi.h"
#import "YFKiwiSample.h"

SPEC_BEGIN(SpecName)

describe(@"ClassName", ^{
    registerMatchers(@"BG"); // 註冊 BGTangentMatcher, BGConvexMatcher 等.

    context(@"a state the component is in", ^{
        let(variable, ^{ // 在每個包含的 "it" 執行前執行執行一次.
            return [[YFKiwiSample alloc]init];
        });

        beforeAll(^{ // 執行一次
            NSLog(@"beforAll");
        });

        afterAll(^{ // Occurs once
            NSLog(@"afterAll");
        });

        beforeEach(^{ // 在每個包含的 "it" 執行前,都執行一次.
            NSLog(@"beforeEach");
        });

        afterEach(^{ // 在每個包含的 "it" 執行後,都執行一次.
            NSLog(@"afterEach");
        });

        it(@"should do something", ^{
            NSLog(@"should do something");
//            [[variable should] meetSomeExpectation];
        });

        specify(^{
            NSLog(@"specify");
            [[variable shouldNot] beNil];
        });

        context(@"inner context", ^{
            NSLog(@"inner context");
            it(@"does another thing", ^{
                NSLog(@"does another thing");
            });

            pending(@"等待實現的東西", ^{
                NSLog(@"等待實現的東西");
            });
        });
    });
});

SPEC_END

期望

期望,用來驗證用例中的物件行為是否符合你的語氣.一個期望,具有如下形式: [[subject should] someCondition:anArgument].此處 [subject should]是表示式的型別, ... someCondition:anArgument] 是匹配器的表示式.

示例:

// 可以用下面的內容替換原來的tests.m中的內容,然後cmd+u
// ;測試失敗可自行解決;解決不了的,繼續往下看.
#import "Kiwi.h"
#import "YFKiwiCar.h"

SPEC_BEGIN(CarSpec)

describe(@"YFKiwiCar", ^{
    it(@"A Car Rule", ^{
        id car = [YFKiwiCar new];
        [[car shouldNot] beNil];
        [[car should] beKindOfClass:[YFKiwiCar class]];
        [[car shouldNot] conformToProtocol:@protocol(NSCopying)];
        [[[car should] have:4] wheels];
        [[theValue([(YFKiwiCar *)car speed]) should] equal:theValue(42.0f)];
        [[car should] receive:@selector(changeToGear:) withArguments: theValue(3)]; 

        [car changeToGear: 3];
    });
});

SPEC_END

should 和 shouldNot

[subject should][subject shouldNot] 表示式,類似於一個接收器,用於接收一個期望匹配器.他們後面緊跟的是真實的匹配表示式,這些表示式將真正被用於計算.

預設地,主語守衛(一種機制,可以保證nil不引起崩潰)也會在[subject should ][subject shouldNot]被使用時建立.給 nil 傳送訊息,通常不會有任何副作用.但是,你幾乎不會希望:一個表示式,只是為了給某個物件傳遞一個無足輕重的訊息,就因為物件本身是nil.也就說,向nil物件本身傳送訊息,並不會有任何副作用;但是在BBD裡,某個要被傳遞訊息的物件是nil,通常是非預期行為.所以,這些表示式的物件守衛機制,會將左側無法判定為不為nil的表示式判定為 fail失敗.

標量裝箱

“裝箱”是固定術語譯法,其實即使我們iOS常說的基本型別轉NSObject型別(事實如此,勿噴).

部分表示式中,匹配器表示式的引數總是NSObject物件.當將一個標量(如int整型,float浮點型等)用於需要id型別引數的地方時,應使用theValue(一個標量)巨集將標量裝箱.這種機制也適用於: 當一個標量需要是一個表示式的主語(主謂賓,基本語法規則,請自行腦補)時,或者一個 存根 的值需要是一個標量時.

示例:

[[theValue(1 + 1) should] equal:theValue(2)];
[[theValue(YES) shouldNot] equal:theValue(NO)];
[[theValue(20u) should] beBetween:theValue(1) and:theValue(30.0)];

YFKiwiCar * car = [YFKiwiCar new];
[[theValue(car.speed) should] beGreaterThan:theValue(40.0f)];

訊息模式

在iOS中,常將呼叫某個例項物件的方法成為給這個物件傳送了某個訊息.所以”訊息模式”中的”訊息”,更多的指的的例項物件的方法;”訊息模式”也就被用來判斷物件的某個方法是否會呼叫以及是否會按照預期的方式呼叫.

一些 Kiwi 匹配器支援使用訊息模式的期望.訊息模式部分,常被放在一個表示式的後部,就像一個將要發給主語的訊息一樣.

示例:

YFKiwiCar * cruiser = [[YFKiwiCar alloc]init];

[[cruiser should] receive:@selector(jumpToStarSystemWithIndex:) withArguments: theValue(3)];

[cruiser jumpToStarSystemWithIndex: 3];

期望:數值 和 數字

  • [[subject shouldNot] beNil]
  • [[subject should] beNil]
  • [[subject should] beIdenticalTo:(id)anObject] - 比較是否完全相同
  • [[subject should] equal:(id)anObject]
  • [[subject should] equal:(double)aValue withDelta:(double)aDelta]
  • [[subject should] beWithin:(id)aDistance of:(id)aValue]
  • [[subject should] beLessThan:(id)aValue]
  • [[subject should] beLessThanOrEqualTo:(id)aValue]
  • [[subject should] beGreaterThan:(id)aValue]
  • [[subject should] beGreaterThanOrEqualTo:(id)aValue]
  • [[subject should] beBetween:(id)aLowerEndpoint and:(id)anUpperEndpoint]
  • [[subject should] beInTheIntervalFrom:(id)aLowerEndpoint to:(id)anUpperEndpoint]
  • [[subject should] beTrue]
  • [[subject should] beFalse]
  • [[subject should] beYes]
  • [[subject should] beNo]
  • [[subject should] beZero]

期望: 子串匹配

  • [[subject should] containString:(NSString*)substring]
  • [[subject should] containString:(NSString*)substring options:(NSStringCompareOptions)options]
  • [[subject should] startWithString:(NSString*)prefix]
  • [[subject should] endWithString:(NSString*)suffix]

示例:

    [[@"Hello, world!" should] containString:@"world"];
    [[@"Hello, world!" should] containString:@"WORLD" options:NSCaseInsensitiveSearch];
    [[@"Hello, world!" should] startWithString:@"Hello,"];
    [[@"Hello, world!" should] endWithString:@"world!"];

期望: 正則表示式匹配

  • [[subject should] matchPattern:(NSString*)pattern]
  • [[subject should] matchPattern:(NSString*)pattern options:(NSRegularExpressionOptions)options]
    [[@"ababab" should] matchPattern:@"(ab)+"];
    [[@" foo " shouldNot] matchPattern:@"^foo$"];
    [[@"abABab" should] matchPattern:@"(ab)+" options:NSRegularExpressionCaseInsensitive];

期望: 數量的變化

  • [[theBlock(^{ ... }) should] change:^{ return (NSInteger)count; }]
  • [[theBlock(^{ ... }) should] change:^{ return (NSInteger)count; } by:+1]
  • [[theBlock(^{ ... }) should] change:^{ return (NSInteger)count; } by:-1]

示例:

    it(@"Expectations: Count changes", ^{
        NSMutableArray * array = [NSMutableArray arrayWithCapacity: 42];

        [[theBlock(^{
            [array addObject:@"foo"];
        }) should] change:^{
            return (NSInteger)[array count];
        } by:+1];

        [[theBlock(^{
            [array addObject:@"bar"];
            [array removeObject:@"foo"];
        }) shouldNot] change:^{ return (NSInteger)[array count]; }];

        [[theBlock(^{
            [array removeObject:@"bar"];
        }) should] change:^{ return (NSInteger)[array count]; } by:-1];
    });

期望: 物件測試

  • [[subject should] beKindOfClass:(Class)aClass]
  • [[subject should] beMemberOfClass:(Class)aClass]
  • [[subject should] conformToProtocol:(Protocol *)aProtocol]
  • [[subject should] respondToSelector:(SEL)aSelector]

期望: 集合

對於集合主語(即,主語是集合型別的):

  • [[subject should] beEmpty]
  • [[subject should] contain:(id)anObject]
  • [[subject should] containObjectsInArray:(NSArray *)anArray]
  • [[subject should] containObjects:(id)firstObject, ...]
  • [[subject should] haveCountOf:(NSUInteger)aCount]
  • [[subject should] haveCountOfAtLeast:(NSUInteger)aCount]
  • [[subject should] haveCountOfAtMost:(NSUInteger)aCount]

對於集合鍵(即此屬性/方法名對應/返回一個集合型別的物件):

  • [[[subject should] have:(NSUInteger)aCount] collectionKey]
  • [[[subject should] haveAtLeast:(NSUInteger)aCount] collectionKey]
  • [[[subject should] haveAtMost:(NSUInteger)aCount] collectionKey]

如果主語是一個集合(比如 NSArray陣列), coollectionKey 可以是任何東西(比如 items),只要遵循語法結構就行.否則, coollectionKey應當是一個可以傳送給主語並返回集合型別資料的訊息.

更進一步說: 對於集合型別的主語,coollectionKey的數量總是根據主語的集合內的元素數量, coollectionKey 本身並無實際意義.

示例:

    NSArray *array = [NSArray arrayWithObject:@"foo"];
    [[array should] have:1] item];

    Car *car = [Car car];
    [car setPassengers:[NSArray arrayWithObjects:@"Eric", "Stan", nil]];
    [[[[car passengers] should] haveAtLeast:2] items];
    [[[car should] haveAtLeast:2] passengers];

期望: 互動和訊息

這些期望用於驗證主語是否在從建立期望到用例結束的這段時間裡接收到了某個訊息(或者說物件的某個方法是否被呼叫).這個期望會同時儲存 選擇器或引數等資訊,並依次來決定期望是否滿足.

這些期望可用於真實或模擬的獨享,但是在設定 receive 表示式時,Xcode 可能會給警告(報黃).

對引數無要求的選擇器:

  • [[subject should] receive:(SEL)aSelector]
  • [[subject should] receive:(SEL)aSelector withCount:(NSUInteger)aCount]
  • [[subject should] receive:(SEL)aSelector withCountAtLeast:(NSUInteger)aCount]
  • [[subject should] receive:(SEL)aSelector withCountAtMost:(NSUInteger)aCount]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCount:(NSUInteger)aCount]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCountAtLeast:(NSUInteger)aCount]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCountAtMost:(NSUInteger)aCount]

含有指定引數的選擇器:

  • [[subject should] receive:(SEL)aSelector withArguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector withCount:(NSUInteger)aCount arguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector withCountAtLeast:(NSUInteger)aCount arguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector withCountAtMost:(NSUInteger)aCount arguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withArguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCount:(NSUInteger)aCount arguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCountAtLeast:(NSUInteger)aCount arguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCountAtMost:(NSUInteger)aCount arguments:(id)firstArgument, ...]

示例:

subject = [Cruiser cruiser];
[[subject should] receive:@selector(energyLevelInWarpCore:) 
    andReturn:theValue(42.0f) withCount:2 arguments:theValue(7)];
[subject energyLevelInWarpCore:7];
float energyLevel = [subject energyLevelInWarpCore:7];
[[theValue(energyLevel) should] equal:theValue(42.0f)];

注意你可以將 any()萬用字元用作引數.如果你只關心一個方法的部分引數的值,這回很有用:

id subject = [Robot robot];
[[subject should] receive:@selector(speak:afterDelay:whenDone:) withArguments:@"Hello world",any(),any()];
[subject speak:@"Hello world" afterDelay:3 whenDone:nil];

期望:通知

  • [[@"MyNotification" should] bePosted];
  • [[@"MyNotification" should] bePostedWithObject:(id)object];
  • [[@"MyNotification" should] bePostedWithUserInfo:(NSDictionary *)userInfo];
  • [[@"MyNotification" should] bePostedWithObject:(id)object andUserInfo:(NSDictionary *)userInfo];
  • [[@"MyNotification" should] bePostedEvaluatingBlock:^(NSNotification *note)block];

Example:

it(@"Notification", ^{
    [[@"自定義通知" should] bePosted];

    NSNotification *myNotification = [NSNotification notificationWithName:@"自定義通知"
                                                                   object:nil];
    [[NSNotificationCenter defaultCenter] postNotification:myNotification];
});

期望: 非同步呼叫

  • [[subject shouldEventually] receive:(SEL)aSelector]
  • [[subject shouldEventually] receive:(SEL)aSelector withArguments:(id)firstArgument, ...]

期望: 異常

  • [[theBlock(^{ ... }) should] raise]
  • [[theBlock(^{ ... }) should] raiseWithName:]
  • [[theBlock(^{ ... }) should] raiseWithReason:(NSString *)aReason]
  • [[theBlock(^{ ... }) should] raiseWithName:(NSString *)aName reason:(NSString *)aReason]

示例:

    [[theBlock(^{
        [NSException raise:@"FooException" reason:@"Bar-ed"];
    }) should] raiseWithName:@"FooException" reason:@"Bar-ed"];

自定義匹配器

Kiwi中,自定義匹配器的最簡單方式是建立KWMatcher的子類,並以適當的方式重寫下面示例中的方法.

為了讓你自定義的匹配器在規則中可用,你需要在規則中使用 registerMatchers(namespacePrefix)進行註冊.

看下Kiwi原始檔中的匹配器寫法(如KWEqualMatcher等),將會使你受益匪淺.

示例:

    // Snippet from AnimalTypeMatcher.m

    #pragma mark Getting Matcher Strings

    // REQUIRED: Return an array of selector strings for the expectations this
    // matcher is used for.
    //
    // For example, this matcher handles [[subject should] beTypeOfMammal:] and
    // [[subject should] beTypeOfInsect:].
    + (NSArray *)matcherStrings {
        return [NSArray arrayWithObjects:@"beTypeOfMammal:", @"beTypeOfInsect:", nil];
    }

    #pragma mark Matching

    // REQUIRED: Evaluate the predicate here.
    // self.subject is available automatically.
    // self.otherSubject is a member variable you would have declared yourself.
    - (BOOL)evaluate {
        return [[self.subject animalType] isEqual:self.otherSubject];
    }

    #pragma mark Getting Failure Messages

    // REQUIRED: Return a custom error message for when "should" is used.
    - (NSString *)failureMessageForShould {
        return @"expected subject to be an animal or insect";
    }

    // OPTIONAL: If you don't override this, Kiwi uses -failureMessageForShould: and
    // replaces the first "to" with "not to".
    - (NSString *)failureMessageForShouldNot {
        return @"expected subject not to be an animal or insect";
    }

    #pragma mark Configuring Matchers

    // These methods should correspond to the selector strings returned in +matcherStrings.
    //
    // Use them to finish configuring your matcher so that -evaluate can be called
    // successfully later. Being a subclass of KWMatcher handles other details like
    // setting up self.subject.

    - (void)beTypeOfMammal:(id)anObject {
      self.otherSubject = anObject;
    }

    - (void)beTypeOfInsect:(id)anObject {
      self.otherSubject = anObject;
    }

模擬物件

模擬物件模擬某個類,或者遵循某個寫一個.他們讓你在完全功能完全實現之前,就能更好地專注於物件間的互動行為,並且能降低物件間的依賴–模擬或比避免那些執行規則時幾乎很難出現的情況.

it(@"Mock", ^{
    id carMock = [YFKiwiCar mock];
    [ [carMock should] beMemberOfClass:[YFKiwiCar class]];
    [ [carMock should] receive:@selector(currentGear) andReturn:theValue(3)];
    [ [theValue([carMock currentGear]) should] equal:theValue(3)];

    id carNullMock = [YFKiwiCar nullMock];
    [ [theValue([carNullMock currentGear]) should] equal:theValue(0)];
    [carNullMock applyBrakes];

    id flyerMock = [KWMock mockForProtocol:@protocol(YFKiwiFlyingMachine)];
    [ [flyerMock should] conformToProtocol:@protocol(YFKiwiFlyingMachine)];
    [flyerMock stub:@selector(dragCoefficient) andReturn:theValue(17.0f)];

    id flyerNullMock = [KWMock nullMockForProtocol:@protocol(YFKiwiFlyingMachine)];
    [flyerNullMock takeOff];
});

模擬 Null 物件

通常模擬物件收到一個非預期的選擇器或訊息模式時,會丟擲異常(PS:iOS開發常見錯誤奔潰之一).在模擬物件上使用 stubreceive期望,期望的訊息會自動新增到模擬物件上,以實現對方法的模擬.

如果你不關心模擬物件如何處理其他非預期的訊息,也不想在收到非預期訊息時丟擲異常,那就使用 null 模擬物件吧(也即 null 物件).

模擬類的例項

建立類的模擬例項(NSObject 擴充套件):

  • [SomeClass mock]
  • [SomeClass mockWithName:(NSString *)aName]
  • [SomeClass nullMock]
  • [SomeClass nullMockWithName:(NSString *)aName]

建立類的模擬例項:

  • [KWMock mockForClass:(Class)aClass]
  • [KWMock mockWithName:(NSString *)aName forClass:(Class)aClass]
  • [KWMock nullMockForClass:(Class)aClass]
  • [KWMock nullMockWithName:(NSString *)aName forClass:(Class)aClass]

模擬協議的例項

建立遵循某協議的例項:

  • [KWMock mockForProtocol:(Protocol *)aProtocol]
  • [KWMock mockWithName:(NSString *)aName forProtocol:(Protocol *)aProtocol]
  • [KWMock nullMockForProtocol:(Protocol *)aProtocol]
  • [KWMock nullMockWithName:(NSString *)aName forProtocol:(Protocol *)aProtocol]

存根

存根,能返回指定定選擇器或訊息模式的封裝好的請求.Kiwi中,你可以存根真實物件(包括類物件)或模擬物件的方法.沒有指定返回值的存根,將會對應返回nil,0等零值.存根需要返回標量的,標量需要使用 theValue(某個標量)巨集 裝箱.

所有的存根都會在規範的一個例子的末尾(一個itblock)被清除.

存根選擇器:

  • [subject stub:(SEL)aSelector]
  • [subject stub:(SEL)aSelector andReturn:(id)aValue]

存根訊息模式:

  • [ [subject stub] *messagePattern*]
  • [ [subject stubAndReturn:(id)aValue] *messagePattern*]

示例:

    id cruiser = [Cruiser cruiser];
    [ [cruiser stubAndReturn:theValue(42.0f)] energyLevelInWarpCore:7];
    float energyLevel = [cruiser energyLevelInWarpCore:7];
    [ [theValue(energyLevel) should] equal:theValue(42.0f)];

    [Cruiser stub:@selector(classification) andReturn:@"Not a moon"];
    [ [ [Cruiser classification] should] equal:@"Not a moon"];

    id mock = [Animal mock];
    [mock stub:@selector(species) andReturn:@"P. tigris"];
    [ [mock.species should] equal:@"P. tigris"];

捕捉引數

有時,你可能想要捕捉傳遞給模擬物件的引數.比如,引數可能沒有是一個沒有很好實現 isEqual: 的物件,如果你想確認傳入的引數是否是需要的,那就要單獨根據某種自定義規則去驗證.另外一種情況,也是最長遇到的情況,就是模擬物件接收的訊息的某個引數是一個block;通常必須捕捉並執行這個block才能確認這個block的行為.

示例:

id robotMock = [KWMock nullMockForClass:[YFKiwiCar class]];
KWCaptureSpy *spy = [robotMock captureArgument:@selector(speak:afterDelay:whenDone:) atIndex:2];

[[robotMock should] receive:@selector(speak:) withArguments:@"Goodbye"];

[robotMock speak:@"Hello" afterDelay:2 whenDone:^{
    [robotMock speak:@"Goodbye"];
}];

void (^block)(void) = spy.argument;
block();

存根的記憶體管理問題

未來的某天,你或許需要存根alloc等法官法.這可能不是一個好主意,但是如果你堅持,Kiwi也是支援的.需要提前指出的是,這麼做需要深入思考某些細節問題,比如如何管理初始化.

Kiwi 存根遵循 Objective-C 的記憶體管理機制.當存根將返回值寫入一個物件時,如果選擇器是以alloc,或new開頭,或含有 copy時,retain訊息將會由存根自動在物件傳送前傳送.

因此,呼叫者不需要特別處理由存根返回的物件的記憶體管理問題.

警告

Kiwi深度依賴Objective-C的執行時機制,包括訊息轉發(比如 forwardInvocation:).因為Kiwi需要預先判斷出來哪些方法可以安全呼叫.使用Kiwi時,有一些慣例,也是你需要遵守的.

為了使情況簡化和有條理,某些方法/選擇器,是決不能在訊息模式中使用,接收期望,或者被存根;否則它們的常規行為將會被改變.不支援使用這些控制器,而且使用後的程式碼的行為結果也會變的很奇怪.

在實踐中,對於高質量的程式程式碼,你可能不需要擔心這些,但是最好還是對這些有些印象.

黑名單(使用有風險):

  • 所有不在白名單中的NSObject類方法和NSObject協議中的方法.(比如-class, -superclass, -retain, -release等.)
  • 所有的Kiwi物件和方法.

白名單(可安全使用):

  • +alloc
  • +new
  • +copy
  • -copy
  • -mutableCopy
  • -isEqual:
  • -description
  • -hash
  • -init
  • 其他任何不在NSObject類或NSobject協議中的方法.

非同步測試

iOS應用經常有元件需要在後臺和主執行緒中內容溝通.為此,Kiwi支援非同步測試;因此就可以進行整合測試-一起測試多個物件.

expectFutureValue() 和 shouldEventually

為了設定非同步測試,你 必須 使用 expectFutureValue 裝箱,並且使用 shouldEventuallyshouldEventuallyBeforeTimingOutAfter來驗證.

shouldEventually 預設在判定為失敗前等待一秒.

[[expectFutureValue(myObject) shouldEventually] beNonNil];

標量的處理

當主語中含有標量時,應該使用 expectFutureValue中使用 theValue裝箱標量.例如:

[[expectFutureValue(theValue(myBool)) shouldEventually] beYes];

shouldEventuallyBeforeTimingOutAfter()

這個block預設值是2秒而不是1秒.

[[expectFutureValue(fetchedData) shouldEventuallyBeforeTimingOutAfter(2.0)] equal:@"expected response data"];

反轉

也有shouldNotEventuallyshouldNotEventuallyBeforeTimingOutAfter 的變體.

一個基於LRResty的示例:

這個block會在匹配器滿足或者超時(預設: 1秒)時完成.

This will block until the matcher is satisfied or it times out (default: 1s)

    context(@"Fetching service data", ^{
        it(@"should receive data within one second", ^{

            __block NSString *fetchedData = nil;

            [[LRResty client] get:@"http://www.example.com" withBlock:^(LRRestyResponse* r) {
                NSLog(@"That's it! %@", [r asString]);
                fetchedData = [r asString];
            }];

            [[expectFutureValue(fetchedData) shouldEventually] beNonNil];

        });

    });