Подтвердить что ты не робот

Stubbing/mocking upservices для приложения iOS

Я работаю над iOS-приложением, основной целью которого является общение с набором удаленных веб-сервисов. Для тестирования интеграции я хотел бы иметь возможность запускать свое приложение против каких-то поддельных веб-сервисов, которые имеют предсказуемый результат.

До сих пор я видел два предложения:

  • Создайте веб-сервер, который обслуживает статические результаты для клиента (например здесь).
  • Реализовать другой код обмена webservice, который на основе флага времени компиляции вызовет либо веб-службы, либо код, который будет загружать ответы из локального файла (example и другой).

Мне любопытно, что сообщество думает о каждом из этих подходов и есть ли там какие-либо инструменты для поддержки этого рабочего процесса.

Обновить. Позвольте мне указать конкретный пример. У меня есть форма входа в систему, в которой указаны имя пользователя и пароль. Я бы хотел проверить два условия:

Итак, мне нужен код для проверки параметра имени пользователя и выдача соответствующего ответа на меня. Надеюсь, что вся логика, которая мне нужна в "поддельном веб-сервисе". Как мне это сделать чисто?

4b9b3361

Ответ 1

Что касается опции 1, я в прошлом использовал это с помощью CocoaHTTPServer и встраивал сервер непосредственно в тест OCUnit:

https://github.com/robbiehanson/CocoaHTTPServer

Я использую код для использования этого в unit test здесь: https://github.com/quellish/UnitTestHTTPServer

В конце концов, HTTP - это просто запрос/ответ.

Издеваться над веб-сервисом, помогая создавать макетный HTTP-сервер или создавая макет веб-сервиса в коде, будет примерно такой же объем работы. Если у вас есть тестовые коды X-кода, у вас есть, по крайней мере, X-коды кода для обработки вашего макета.

Для варианта 2, чтобы издеваться над веб-службой, вы не будете общаться с веб-службой, вместо этого вы будете использовать макет-объект, который имеет известные ответы. [MyCoolWebService performLogin:username withPassword:password]

станет в вашем тесте

[MyMockWebService performLogin:username withPassword:password] Ключевым моментом является то, что MyCoolWebService и MyMockWebService используют один и тот же контракт (в objective-c, это будет протокол). У OCMock есть много документации, чтобы вы начали.

Однако для теста интеграции вы должны тестировать реальный веб-сервис, например, среду QA/промежуточного уровня. То, что вы на самом деле описываете, больше похоже на функциональное тестирование, чем на тестирование интеграции.

Ответ 2

Я предлагаю использовать Nocilla. Nocilla - это библиотека для обнуления HTTP-запросов с помощью простой DSL.

Скажем, что вы хотите вернуть 404 с google.com. Все, что вам нужно сделать, это:

stubRequest(@"GET", "http://www.google.com").andReturn(404); // Yes, it ObjC

После этого любой HTTP to google.com вернет 404.

Более полный пример, в котором вы хотите сопоставить POST с определенным телом и заголовками и вернуть законченный ответ:

stubRequest(@"POST", @"https://api.example.com/dogs.json").
withHeaders(@{@"Accept": @"application/json", @"X-CUSTOM-HEADER": @"abcf2fbc6abgf"}).
withBody(@"{\"name\":\"foo\"}").
andReturn(201).
withHeaders(@{@"Content-Type": @"application/json"}).
withBody(@"{\"ok\":true}");

Вы можете сопоставить любой запрос и подделать любой ответ. Проверьте README для получения более подробной информации.

Преимущества использования Nocilla над другими решениями:

  • Это быстро. Нет HTTP-серверов для запуска. Ваши тесты будут выполняться очень быстро.
  • Никаких сумасшедших зависимостей для управления. Кроме того, вы можете использовать CocoaPods.
  • Он хорошо протестирован.
  • Отличный DSL, который сделает ваш код очень легким для понимания и поддержки.

Основное ограничение заключается в том, что он работает только с инфраструктурами HTTP, созданными поверх NSURLConnection, такими как AFNetworking, MKNetworkKit или plain NSURLConnection.

Надеюсь, это поможет. Если вам нужно что-нибудь еще, я здесь, чтобы помочь.

Ответ 3

Я предполагаю, что вы используете Objective-C. Для Objective-C OCMock широко используется для издевательского/модульного тестирования (ваш второй вариант).

Я использовал OCMock в последний раз больше, чем год назад, но насколько я помню, это полноценная фальшивая каркас и может делать все, что описано ниже.

Одна важная вещь в mocks заключается в том, что вы можете использовать столько или меньше фактической функциональности своих объектов. Вы можете создать "пустой" макет (который будет иметь все методы - ваш объект, но ничего не сделает) и переопределить только те методы, которые вам нужны в вашем тесте. Обычно это делается при тестировании других объектов, которые полагаются на макет.

Или вы можете создать макет, который будет действовать как ваш реальный объект, и выровнять некоторые методы, которые вы не хотите тестировать на этом уровне (например, - методы, которые фактически обращаются к базе данных, требуют сетевого подключения и т.д.), Обычно это делается, когда вы тестируете сам издевавшийся объект.

Важно понимать, что вы не создаете mocks раз и навсегда. Каждый тест может создавать mocks для одних и тех же объектов заново на основе того, что тестируется.

Еще одна важная вещь в mocks заключается в том, что вы можете "записывать" сценарий (последовательности вызовов) и ваши "ожидания" о них (какие методы за кулисами следует вызывать, с какими параметрами и в каком порядке) повторить "сценарий - тест потерпит неудачу, если ожидания не будут выполнены. Это основное различие между классическим и mockist TDD. Он имеет свои плюсы и минусы (см. Статью Мартина Фаулера).

Теперь рассмотрим ваш конкретный пример (я буду использовать псевдосинтакс, который больше похож на С++ или Java, а не Objective C):

Предположим, что у вас есть объект класса LoginForm, который представляет введенную регистрационную информацию. Он имеет (среди прочих) методы setName(String), setPassword(String), bool authenticateUser() и Authenticator* getAuthenticator().

У вас также есть объект класса Authenticator, который имеет (среди прочих) методы bool isRegistered(String user), bool authenticate(String user, String password) и bool isAuthenticated(String user).

Здесь вы можете проверить некоторые простые сценарии:

Создать MockLoginForm mock со всеми путями, за исключением четырех указанных выше. Первые три метода будут использовать фактическую реализацию LoginForm; getAuthenticator() будет пропущен, чтобы вернуться MockAuthenticator.

Создайте MockAuthenticator mock, который будет использовать некоторую фальшивую базу данных (например, внутреннюю структуру данных или файл) для реализации трех своих методов. База данных будет содержать только один кортеж: ('rightuser','rightpassword').

TestUserNotRegistered

Сценарий воспроизведения:

MockLoginForm.setName('wronuser');
MockLoginForm.setPassword('foo');
MockLoginForm.authenticate();

ожидания:

getAuthenticator() is called
MockAuthenticator.isRegistered('wrognuser') is called and returns 'false'

TestWrongPassword

Сценарий воспроизведения:

MockLoginForm.setName('rightuser');
MockLoginForm.setPassword('foo');
MockLoginForm.authenticate();

ожидания:

getAuthenticator() is called
MockAuthenticator.isRegistered('rightuser') is called and returns 'true'
MockAuthenticator.authenticate('rightuser','foo') is called and returns 'false'

TestLoginOk

Сценарий воспроизведения:

MockLoginForm.setName('rightuser');
MockLoginForm.setPassword('rightpassword');
MockLoginForm.authenticate();
result = MockAuthenticator.isAuthenticated('rightuser')

ожидания:

getAuthenticator() is called
MockAuthenticator.isRegistered('rightuser') is called and returns 'true'
MockAuthenticator.authenticate('rightuser','rightpassword') is called and returns 'true'
result is 'true'

Надеюсь, это поможет.

Ответ 4

Вы можете сделать mock web-сервис довольно эффективно с подклассом NSURLProtocol:

Заголовок:

@interface MyMockWebServiceURLProtocol : NSURLProtocol
@end

Реализация:

@implementation MyMockWebServiceURLProtocol

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    return [[[request URL] scheme] isEqualToString:@"mymock"];
}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
    return request;
}

+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
    return [[a URL] isEqual:[b URL]];
}

- (void)startLoading
{
    NSURLRequest *request = [self request];
    id <NSURLProtocolClient> client = [self client];
    NSURL *url = request.URL;
    NSString *host = url.host;
    NSString *path = url.path;
    NSString *mockResultPath = nil;
    /* set mockResultPath here … */
    NSString *fileURL = [[NSBundle mainBundle] URLForResource:mockResultPath withExtension:nil];
    [client URLProtocol:self
 wasRedirectedToRequest:[NSURLRequest requestWithURL:fileURL]
       redirectResponse:[[NSURLResponse alloc] initWithURL:url
                                                  MIMEType:@"application/json"
                                     expectedContentLength:0
                                          textEncodingName:nil]];
    [client URLProtocolDidFinishLoading:self];
}

- (void)stopLoading
{
}

@end

Интересной процедурой является -startLoading, в которой вы должны обработать запрос и найти статический файл, соответствующий ответу в комплекте приложения, перед перенаправлением клиента на этот файл.

Вы устанавливаете протокол с помощью

[NSURLProtocol registerClass:[MyMockWebServiceURLProtocol class]];

И ссылайтесь на него с такими URL-адресами, как

mymock://mockhost/mockpath?mockquery

Это значительно проще, чем реализация реального веб-сервиса либо на удаленной машине, либо локально внутри приложения; компромисс заключается в том, что симуляция заголовков HTTP-ответов намного сложнее.

Ответ 5

OHTTPStubs - это отличная рамка для того, чтобы делать то, что вы хотите, получившее много тяги. Из их github readme:

OHTTPStubs - это библиотека, предназначенная для быстрого отключения сетевых запросов. Это может помочь вам:

  • Проверяйте свои приложения с поддельными сетевыми данными (заглушенными из файла) и имитируйте медленные сети, чтобы проверить поведение вашего приложения в плохих условиях сети.
  • Записывайте тесты устройств, которые используют поддельные сетевые данные из ваших светильников.

Он работает с NSURLConnection, новыми iOS7/OSX.9 NSURLSession, AFNetworking (и 1.x и 2.x), либо любой сетевой инфраструктурой, использующей систему загрузки URL Cocoa.

Заголовки OHHTTPStubs полностью документируются с использованием комментариев, похожих на заголовок в форме заголовка. Здесь вы также можете прочитать онлайн-документацию.

Вот пример:

[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
    return [request.URL.host isEqualToString:@"mywebservice.com"];
} withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {
    // Stub it with our "wsresponse.json" stub file
    NSString* fixture = OHPathForFileInBundle(@"wsresponse.json",nil);
    return [OHHTTPStubsResponse responseWithFileAtPath:fixture
              statusCode:200 headers:@{@"Content-Type":@"text/json"}];
}];

Вы можете найти дополнительные примеры использования на странице wiki.