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

时间:2014-03-21 14:02:41

标签: ios objective-c automated-tests ui-automation kif

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

问题是位置服务启动BEFORE KIF方法-beforeAll调用。 所以嘲笑为时已晚。

任何建议都将不胜感激。

2 个答案:

答案 0 :(得分:3)

在我的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

Cleaner将在您的应用程序目标中具有CLLocationManager的子类,并且在您的测试目标中具有相同名称的另一个子类,如上所示发送假位置。但是,如果这可能取决于您的测试目标的设置方式,因为它实际上需要成为Calabash使用它的应用程序目标。


另一种方式:

    项目中的
  • 创建另一个配置“Testing”,克隆“Debug”

  • Preprocessor Macro TESTING=1添加到该配置中。

  • 子类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
    
  • 在您的测试目标方案中选择新配置


还有另一种选择:

enter image description here

可能是最好的:不需要更改代码。

答案 1 :(得分:1)

像往常一样,有两种方法可以做到这一点。关键是不要试图模拟现有的位置服务,而是要有一个完全不同的模拟,你可以在运行时访问。我要描述的第一种方法基本上是构建自己的小型DI容器。第二种方法是获取您通常无法访问的单身人士。

1)重构您的代码,使其不直接使用LocationService。相反,将其封装在一个持有者中(可能是一个简单的单例类)。然后,让你的持有人测试意识。这样做的方式是你有一个类似于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的单身人士。这是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.

请参阅链接以获取超级实现详细信息。疯狂的道具给Matt Gallagher最初的想法。如果需要,我也可以把文件发给你。

结论:DI是一件好事。人们抱怨不得不重构和不得不改变你的代码只是为了测试,但测试可能是质量软件开发中最重要的部分,DI + ApplicationContext使事情变得如此简单。我们使用Typhoon框架,但如果您正在进行任何级别的测试,即使自己动手并采用DI + ApplicationContext模式也非常值得。