Ios 如何使用KIF框架模拟位置服务
我使用KIF框架()进行UI测试 我需要模拟定位服务 问题是位置服务在调用KIF方法之前启动。 所以现在嘲笑已经太迟了Ios 如何使用KIF框架模拟位置服务,ios,objective-c,automated-tests,ui-automation,kif,Ios,Objective C,Automated Tests,Ui Automation,Kif,我使用KIF框架()进行UI测试 我需要模拟定位服务 问题是位置服务在调用KIF方法之前启动。 所以现在嘲笑已经太迟了 任何建议都将不胜感激。在我的KIF目标中,我有一个BaseKIFSearchTestCase:KIFTestCase,我将覆盖类别中CLLocationManager的startUpdatingLocation 请注意,这是我做过的唯一一个类别覆盖,因为这通常不是一个好主意。但在测试目标中,我可以接受它 #import <CoreLocation/CoreLocation
任何建议都将不胜感激。在我的KIF目标中,我有一个
BaseKIFSearchTestCase:KIFTestCase
,我将覆盖类别中CLLocationManager的startUpdatingLocation
请注意,这是我做过的唯一一个类别覆盖,因为这通常不是一个好主意。但在测试目标中,我可以接受它
#import <CoreLocation/CoreLocation.h>
#ifdef TARGET_IPHONE_SIMULATOR
@interface CLLocationManager (Simulator)
@end
@implementation CLLocationManager (Simulator)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
-(void)startUpdatingLocation
{
CLLocation *fakeLocation = [[CLLocation alloc] initWithLatitude:41.0096334 longitude:28.9651646];
[self.delegate locationManager:self didUpdateLocations:@[fakeLocation]];
}
#pragma clang diagnostic pop
@end
#endif // TARGET_IPHONE_SIMULATOR
#import "BaseKIFSearchTestCase.h"
@interface BaseKIFSearchTestCase ()
@end
@implementation BaseKIFSearchTestCase
//...
@end
还有一个选择:
可能是最好的:不需要更改代码。像往常一样,有几种方法可以做到这一点。关键不在于试图模拟现有的位置服务,而是要有一个在运行时可以访问的完全不同的模拟。我要描述的第一种方法基本上是构建自己的微型DI容器。第二种方法是获取通常无法访问的单例 1) 重构代码,使其不直接使用LocationService。相反,将其封装在一个holder中(可以是一个简单的单例类)。然后,让您的持有者测试知道。其工作原理是您拥有类似LocationServiceHolder的东西,它具有:
// Do some init for your self.realService and make this holder
// a real singleton.
+ (LocationService*) locationService {
return useMock ? self.mockService : self.realService;
}
- (void)useMock:(BOOL)useMock {
self.useMock = useMock;
}
- (void)setMock:(LocationService*)mockService {
self.mockService = mockService;
}
然后,每当您需要定位服务时,请致电
[[LocationServiceHolder sharedService] locationService];
因此,当您进行测试时,您可以执行以下操作:
- (void)beforeAll {
id mock = OCClassMock([LocationService class]);
[[LocationServiceHolder sharedService] useMock:YES]];
[[LocationServiceHolder sharedService] setMock:mock]];
}
- (void)afterAll {
[[LocationServiceHolder sharedService] useMock:NO]];
[[LocationServiceHolder sharedService] setMock:nil]];
}
#include "MockThirdPartyService.h"
...
id mock = OCClassMock([ThirdPartyService class]);
[ThirdPartyService setSharedInstance:mock];
// set up your mock and do your testing here
// Once you're done, clean up.
[ThirdPartyService setSharedInstance:nil];
// Now your singleton is no longer mocked and additional tests that
// don't depend on mock behavior can continue running.
当然,您可以在beforeach中这样做,并重写语义,使其比我在这里展示的基本版本要好一点
2) 如果您使用的是第三方LocationService,它是一个无法修改的单例,它稍微有点棘手,但仍然可行。这里的技巧是使用一个类别覆盖现有的单例方法,并公开模拟而不是普通的单例。技巧中的技巧是,如果mock不存在,则能够将消息发送回原始单例
假设你有一个叫第三方服务的单身汉。这是MockThirdPartyService.h:
static ThirdPartyService *mockThirdPartyService;
@interface ThirdPartyService (Testing)
+ (id)sharedInstance;
+ (void)setSharedInstance:(ThirdPartyService*)instance;
+ (id)mockInstance;
@end
这是MockThirdPartyService.m:
#import "MockThirdPartyService.h"
#import "NSObject+SupersequentImplementation.h"
// Stubbing out ThirdPartyService singleton
@implementation ThirdPartyService (Testing)
+(id)sharedInstance {
if ([self mockInstance] != nil) {
return [self mockInstance];
}
// What the hell is going on here? See http://www.cocoawithlove.com/2008/03/supersequent-implementation.html
IMP superSequentImp = [self getImplementationOf:_cmd after:impOfCallingMethod(self, _cmd)];
id result = ((id(*)(id, SEL))superSequentImp)(self, _cmd);
return result;
}
+ (void)setSharedInstance:(ThirdPartyService *)instance {
mockThirdPartyService = instance;
}
+ (id)mockInstance {
return mockThirdPartyService;
}
@end
要使用,请执行以下操作:
- (void)beforeAll {
id mock = OCClassMock([LocationService class]);
[[LocationServiceHolder sharedService] useMock:YES]];
[[LocationServiceHolder sharedService] setMock:mock]];
}
- (void)afterAll {
[[LocationServiceHolder sharedService] useMock:NO]];
[[LocationServiceHolder sharedService] setMock:nil]];
}
#include "MockThirdPartyService.h"
...
id mock = OCClassMock([ThirdPartyService class]);
[ThirdPartyService setSharedInstance:mock];
// set up your mock and do your testing here
// Once you're done, clean up.
[ThirdPartyService setSharedInstance:nil];
// Now your singleton is no longer mocked and additional tests that
// don't depend on mock behavior can continue running.
请参阅链接以了解超顺序实现的详细信息。Matt Gallagher的原创创意的疯狂道具。如果你需要,我也可以把文件寄给你
结论:DI是一件好事。人们抱怨不得不重构和更改代码只是为了测试,但测试可能是高质量软件开发最重要的部分,而DI+ApplicationContext让事情变得简单多了。我们使用Typhone framework,但如果您正在进行任何级别的测试,即使您自己使用并采用DI+ApplicationContext模式,也非常值得。您可以提供任何示例代码来重现该问题吗?虽然您关于测试代码重构的结论是正确的,但您的答案中没有显示DI,当您切换服务的内部行为而不传入测试服务对象时。“DI和单身汉永远不会相处融洽。”维金戈塞贡多请重读我的答案。我特别指出,只有第一种方法是构建您自己的微型DI,而第二种方法是模拟您无法控制的单例,我从未声称这是DI。DI和单身人士相处得很好。DI允许您维护对象的任何类型的作用域(单例、弱单例等),并且仍然保持可测试性。以前从未听说过“微小DI”这个术语。一定错过了那个课程。@vikingosegundo如果我回答“只使用DI”,我认为这不会有什么帮助。如果OP有DI,这就不是问题了。这个问题的上下文意味着缺乏DI和对DI的认识,因此我给出了一个实现DI系统的基本示例,该系统没有很多特性,但为您提供了一个最小的抽象层。当事情超出你的控制范围时,还有一秒钟。不知道你要说什么,比如“在课堂上一定错过了”。直接批评我的答案。好吧,这里是我的批评者:没有理由让单身汉参与其中。但这正是你的答案所暗示的。为什么?我可以实例化
cllocationmanager
?只要我需要它,我就可以把它传递到任何地方。我不需要围绕它的单例服务,它会打乱我的依赖关系。我只需要一个服务,我将它传递给任何需要它的对象。你的第三个选项看起来很简单,但似乎对我不起作用。我选择旧金山作为我的位置,但是当我运行测试时,我得到了一个失败的位置调用的警报。