序
Specta作为一个优秀的测试框架,不仅能够进行常规的单元测试,也能够测试对象是否存在泄漏。
原理
创建一个容器TestContainer,它weak持有将要检测的对象testObject,然后,我们让其他持有testObject的指针都置为nil,这样就只有这个容器TestContainer在weak持有testObject,我们知道没有强引用这个testObject的时候,testObject的对象会被释放掉, 而TestContainer里面weak引用的指针,也会被置为nil。
而我们会延时几秒钟,检查TestContainer的指针是不是为nil。
具体操作
TestContainer 代码
@interface TestContainer : NSObject
@property (nonatomic, weak) id object;
@end
@implementation TestContainer
// Empty
@end
最后我们会延时TestContainer的object指针是不是为nil。
要检测的对象 代码
@interface TestObject : NSObject
@end
@implementation TestObject
@end
Spec测试代码
下面写检测代码,我们定义一个局部变量testObj, 然后在一个@autoreleasepool里面创建对象, 并且这个testObj在block内部置为nil
describe(@"TestObject", ^{
context(@"when created", ^{
__block TestObject *testObj = nil;
it(@"should be dealloc when not use", ^{
TestContainer * tc = [TestContainer new];
@autoreleasepool {
testObj = [TestObject new];
tc.object = testObj;
testObj = nil;
};
expect(tc.object).after(5).to.beNil();
});
});
});
这里我们断言:断定tc的object指针在5秒钟后,是nil。
检测结果
在Xcode中,按Command + U
开始测试,上面代码中, 我们运行的结果是 Test Success
修改代码
我们故意修改TestObject代码, 让其不能正常释放。
@interface TestObject : NSObject
@end
@implementation TestObject
- (instancetype)init
{
self = [super init];
if (self) {
self.timer = [NSTimer timerWithTimeInterval:1
target:self
selector:@selector(timerAction)
userInfo:nil
repeats:YES];
}
return self;
}
- (void)timerAction {
NSLog(@"hello");
}
@end
检测结果2
Test Fail
更加模板化一点的用法
从上面的代码中,如果您按照上面的操作进行测试,能够发现我们能够检测对象是否正常释放。
在实际项目中,我们知道我们要测试的对象会有很多, 除了上面的检测代码外, 我们想要的会更多。
作为一个程序员, 想的是如何能够更加简单化!!!
写Spec模板代码
利用sharedExamplesFor模板化
sharedExamplesFor(@"dealloc_behavior", ^(NSDictionary *data) {
context(@"release", ^{
it(@"should dealloc", ^{
id (^block)(void) = [[data allValues] firstObject];
if (!block) {
return;
}
TestContainer *container = [TestContainer new];
@autoreleasepool {
id object = nil;
__weak id weakObject = nil;
object = block();
weakObject = object;
expect(weakObject).notTo.beNil();
container.object = weakObject;
object = nil;
}
expect(container.object).after(5).beNil();
});
});
});
这里需要注意的是sharedExamplesFor的第一个参数@"dealloc_behavior",会在下面使用。
Spec测试代码
调用sharedExamplesFor模板化的代码
describe(@"TestObject", ^{
itShouldBehaveLike(@"dealloc_behavior", @{ @"value" : ^{
return [[TestObject alloc] init];
} });
});
注意itShouldBehaveLike的第一个参数, 这个参数就是上面记录的@"dealloc_behavior"。
这样就每次使用5行代码, 就能够检测对象是否被正常释放。
更进一步
这里我们只是检测了这个对象testObj是否正常释放,但是有时这个对象testObj里面新生成了对象innerObj,可能这个testObj正常释放了, 但是innerObj却没有释放。
当然我们可以单独检测innerObj是否正常释放,不过更方便有效的是,我们能够遍历检测testObj里面的子对象,把所有的对象都检测一边。这样就能够更加高效一点的检测对象。