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)
在所有内嵌上下文或当前上下文的```it``block执行之前执行一次.afterAll(aBlock)
在所有内嵌上下文或当前上下文的```it``block执行之后执行一次.beforeEach(aBlock)
在所有包含的上下文环境的it
block执行之前,均各执行一次.用于初始化指定上下文环境的代码,应该放在这里.afterEach(aBlock)
在所有包含的上下文环境的it
block执行之后,均各执行一次.it(aString, aBlock)
声明一个测试用例.这里描述了对对象或行为的期望.specify(aBlock)
声明一个没有描述的测试用例.这个常用于简单的期望.pending(aString, aBlock)
可用于标记尚未完成的功能或用例,仅会使Xcode输出一个黄色警告.(有点TODO的赶脚)let(subject, aBlock)
声明一个本地工具变量,这个变量会在规则内所有上下文的每个it
block执行前,重新初始化一次.
示例.
#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开发常见错误奔溃之一).在模拟对象上使用 stub
或 receive
期望,期望的消息会自动添加到模拟对象上,以实现对方法的模拟.
如果你不关心模拟对象如何处理其他非预期的消息,也不想在收到非预期消息时抛出异常,那就使用 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(某个标量)
宏 装箱.
所有的存根都会在规范的一个例子的末尾(一个it
block)被清除.
存根选择器:
[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
装箱,并且使用 shouldEventually
或 shouldEventuallyBeforeTimingOutAfter
来验证.
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"];
反转
也有shouldNotEventually
和 shouldNotEventuallyBeforeTimingOutAfter
的变体.
一个基于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];
});
});
Kiwi,BDD行为测试框架--iOS攻城狮进阶必备技能的更多相关文章
- iOS攻城狮修炼之路
自己总结的学习iOS的笔记,打造一个全面的知识体系,iOS攻城狮修炼之路[持续更新中] iOS学习笔记01-APP相关 iOS学习笔记02-UIScrollView iOS学习笔记03-UITable ...
- iOS程序猿如何快速掌握 PHP,化身"全栈攻城狮"?
这是一篇以 iOS 开发人员的视角写给广大iOS 程序猿的 PHP 入门指南.在这篇文章里我努力去发掘 objectiv-c 与 php 之间的共性,来帮助有一定 iOS 开发经验的攻城狮来快速上手一 ...
- Fiddler无所不能——之测试开发攻城狮必备利器
Fiddler无所不能——之测试开发攻城狮必备利器 1.模拟真实网络环境4g网.3g网络.2g网络.弱网.请求超时 开启弱网Rules——Performance——勾选Simulate Modem S ...
- Android优秀资源整理合集(论菜鸟到高级攻城狮)
转载请注明转自:http://blog.csdn.net/u011176685/article/details/51434702 csdn文章:Android优秀资源整理合集(论菜鸟到高级攻城狮) 时 ...
- JBPM4之decision节点:3、程序猿|菜鸟|攻城狮|牛人
JBPM入门系列文章: JBPM4入门——1.jbpm简要介绍 JBPM4入门——2.在eclipse中安装绘制jbpm流程图的插件 JBPM4入门——3.JBPM4开发环境的搭建 JBPM4入门—— ...
- 遗留系统:IT攻城狮永远的痛
我常常觉得我们非常幸运,我们现在所处的时代是一个令人振奋的时代,我们进入了软件工业时代.在这个时代里,我们进行软件开发已经不再是一个一个的小作坊,我们在进行着集团化的大规模开发.我们开发的软件不再是为 ...
- 【Copy攻城狮日志】docker搭建jenkins拉取svn代码打包vue项目部署到nginx
↑开局一张图,故事全靠编↑ 前言 打开搜索引擎输入『Copy攻城狮』,发现最新的一条记录已经是去年的4月,意味着我又有一年时间没有再总结成长了.习惯了“温水煮青蛙”的日子,无论是经验水平还是薪资收入, ...
- 攻城狮送女友的CSS3生日蛋糕
在线预览:http://keleyi.com/keleyi/phtml/html5/29.htm 代码如下: <!DOCTYPE html> <html> <head&g ...
- 【Copy攻城狮日志】Node快速重命名文件,告别Potplay字幕困扰问题
↑开局一张图,故事全靠编↑ 前言 Copy攻城狮日志的惯例,开局一张图,开始为您讲述一个鲜为人知的故事.故事的开头要从本大狮从盗版网站下载udemy课程的犯罪伊始说起,去年的某月某天,我真正接触到了“ ...
随机推荐
- g++ 出现 undefined reference to ......
g++ 出现 undefined reference to ...... 检查/usr/local/lib /usr/lib 发现已经存在相应的库文件 那么,问题可能出现在g++链接次序上,即先链接 ...
- .NET平台下使用C#连接各种数据库
在.NET平台下,通常我们需要连接不同的数据库,这就需要我们配置连接字符串以及提供常用的class进行数据存取. 1.C#连接Access @"Provider=Microsoft.ACE. ...
- JavaScript运算符优先级——"++,--,&&,||“
上篇文章比较了"?,="三者的优先级:"?">"=">"," 今天继续学习"++,--,& ...
- 利用COM组件实现对WORD书签处写入值
using System; using System.Collections.Generic; using System.Text; using Microsoft.Office.Interop.Wo ...
- CKEditor4.4.5 插入高度代码及上传图片
1.首先准备所需要的插件 (1). CKEditor4.4.5 下载地址:http://ckeditor.com/download.如果不想下载直接引用CKEditor的CDN也是可以的.cdn地址 ...
- Redis学习1
Redis 学习记录 简介 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zs ...
- dotnetcharting 的简单使用
dotnetcharting 是一个很好用的图表控件,能画出很漂亮的报表,一般常用到的主要有柱状图.饼图.折线图三种. dotnetcharting 有web版.winform版多个版本可供使用,官方 ...
- MySQL中报错: [Err] 1146 - Table 'performance_schema.session_status' doesn't exist 解决办法
解决办法:1.打开cmd 执行命令cd/ 进入C盘根目录2.dir 查看C盘根目录下文件夹 找到 Program Files文件夹3.cd Program Files 进入该文件夹下 再输入dir ...
- Vue.js - Day4
父组件向子组件传值 组件实例定义方式,注意:一定要使用props属性来定义父组件传递过来的数据 <script> // 创建 Vue 实例,得到 ViewModel var vm ...
- 给大家推荐一个.Net的混淆防反编译工具ConfuserEx
给大家推荐一个.Net的混淆防反编译工具ConfuserEx. 由于项目中要用到.Net的混淆防反编译工具. 在网上找了很多.Net混淆或混淆防反编译工具,如.NET Reactor.Dotfusca ...