Как обычно, несколько способов сделать это. Суть не в том, чтобы пытаться сымитировать существующую службу определения местоположения, а создать совершенно другой макет, к которому вы можете получить доступ во время выполнения. Первый метод, который я собираюсь описать, — это создание собственного крошечного контейнера внедрения зависимостей. Второй метод предназначен для доступа к синглтонам, к которым у вас обычно нет доступа.
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;
}
Затем всякий раз, когда вам нужна служба определения местоположения, вы звоните
[[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.
См. ссылку для последующих деталей реализации. Безумный респект Мэтту Галлахеру за оригинальную идею. Я также могу отправить вам файлы, если вам нужно.
Вывод: DI - это хорошо. Люди жалуются на необходимость рефакторинга и изменения кода только для тестирования, но тестирование, вероятно, является наиболее важной частью качественной разработки программного обеспечения, а DI + ApplicationContext значительно упрощает задачу. Мы используем фреймворк Typhoon, но даже создание собственного шаблона DI + ApplicationContext того стоит, если вы выполняете какой-либо уровень тестирования.
person
plluke
schedule
18.02.2015