[iOS 攻城獅進階必備技能] Kiwi:BDD 行爲測試框架

簡介

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

使用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,點擊錯誤提示,會在左側第四導航欄看到相似下面的錯誤:objective-c

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 類的開始和結束,以及測試用例的分組聲明.ruby

  • registerMatchers(aNamespacePrefix) 註冊全部使用指定命名空間前綴的匹配器.除了Kiwi默認的匹配器,這些匹配器也能夠在當前規則中使用.app

  • 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];

        });

    });
相關文章
相關標籤/搜索