我使用KIF框架(http://github.com/kif-framework/KIF)进行UI测试,我需要模拟位置服务。
问题是在调用KIF方法-beforeAll之前启动位置服务。所以现在嘲笑已经太晚了。
如有任何建议,将不胜感激。
发布于 2015-02-18 14:07:08
在我的KIF目标中,我有一个BaseKIFSearchTestCase : KIFTestCase
,其中覆盖了CLLocationManager`s的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
更干净的方法是在您的应用程序目标中有一个CLLocationManager
子类,在您的测试目标中有一个同名的子类,这些子类发送假位置,如上面所示。但是,如果这是可能的,则取决于您的测试目标是如何设置的,因为在Calabash使用它时,它实际上需要成为应用程序目标。
还有另一种方式:
Preprocessor Macro
TESTING=1
添加到该配置中。CLLocationManager
还有另一个选择:
也许是最好的:不需要修改代码。
发布于 2015-02-18 14:35:19
和往常一样,有几种方法可以做到这一点。关键不是试图模拟现有的位置服务,而是要拥有一个完全不同的模拟,您可以在运行时访问它。我要描述的第一个方法基本上是构建您自己的微型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;
}
然后每当你需要你的locationService时,你就打电话给
[[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]];
}
当然,您可以在beforeEach中这样做,并重写语义,使其比我在这里展示的基本版本要好一些。
2)如果您使用的是一个无法修改的第三方LocationService,那么它会稍微复杂一些,但仍然是可行的。这里的诀窍是使用一个类别来覆盖现有的单例方法,并公开模拟而不是普通的单例。技巧中的诀窍是,如果模拟不存在,就能够将消息发送回原始的单例。
因此,假设您有一个名为ThirdPartyService的单例。这是MockThidPartyService.h:
static ThirdPartyService *mockThirdPartyService;
@interface ThirdPartyService (Testing)
+ (id)sharedInstance;
+ (void)setSharedInstance:(ThirdPartyService*)instance;
+ (id)mockInstance;
@end
这是MockThidPartyService.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
要使用,您可以做如下的事情:
#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.
有关超序列实现详细信息,请参阅链接。疯狂的道具为马特加拉格尔最初的想法。如果你需要的话,我也可以把文件发给你。
结论: DI是一件好事。人们抱怨必须重构和更改代码才能进行测试,但是测试可能是高质量软件开发的最重要部分,DI + ApplicationContext使事情变得非常容易。我们使用Typhoon框架,但是如果您正在进行任何级别的测试,即使您自己滚动并采用DI + ApplicationContext模式也是非常值得的。
https://stackoverflow.com/questions/22560990
复制