如何使用KIF框架来模拟位置服务

我使用KIF框架( http://github.com/kif-framework/KIF )进行UItesting,我需要模拟位置服务。

问题是位置服务启动BEFORE KIF方法-beforeAll被调用。 所以嘲笑已经太迟了。

任何build议,将不胜感激。

在我的KIF目标中,我有一个BaseKIFSearchTestCase : KIFTestCase ,我在其中覆盖CLLocationManager的startUpdatingLocation类别。

请注意,这是我所做过的唯一的重写,因为这通常不是一个好主意。 但在一个testing目标中,我可以接受它。

 #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的子类,并且在你的testing目标中有另一个同名的子类发送假的位置,如上所示。 但是,如果这可能取决于你的testing目标是如何设置的,因为它实际上需要成为葫芦使用它的应用目标。


另一种方式:

  • 在你的项目中创build另一个configuration“Testing”,克隆“Debug”

  • Preprocessor Macro TESTING=1添加到该configuration。

  • 子类CLLocationManager

  • 使用你将使用CLLocaltionManger的那个子类

  • 有条件地编译该类

     #import "GELocationManager.h" @implementation GELocationManager -(void)startUpdatingLocation { #if TESTING==1 #warning Testmode dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ CLLocation *fakeLocation = [[CLLocation alloc] initWithLatitude:41.0096334 longitude:28.9651646]; [self.delegate locationManager:self didUpdateLocations:@[fakeLocation]]; }); #else [super startUpdatingLocation]; #endif } @end 
  • 在您的testing目标scheme中select新的configuration


还有一个select:

在这里输入图像说明

可能是最好的:没有代码需要改变。

像往常一样,有几个方法来做到这一点。 关键不是试图嘲弄现有的位置服务,而是要有一个完全不同的模拟,你可以在运行时访问。 我要描述的第一种方法基本上是构build你自己的微型DI容器。 第二种方法是获取你通常不能访问的单例。

1)重构你的代码,使它不直接使用LocationService。 相反,封装在一个持有人(可能是一个简单的单身类)。 然后,让持有者testing意识。 这是工作的方式是你有像一个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]; 

所以当你testing的时候,你可以做如下的事情:

 - (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的单例。 这里是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 

要使用,你会做这样的事情:

 #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是一件好事。 人们抱怨必须重构并且不得不更改代码来进行testing,但testing可能是高质量软件开发中最重要的部分,而DI + ApplicationContext使事情变得更容易。 我们使用Typhoon框架,但是如果您正在进行任何级别的testing,那么即使是自己开发并采用DI + ApplicationContext模式也是非常值得的。