2014-02-14 3 views
4

Я пытаюсь написать модульные тесты для приложения iOS, которое использует фреймворк базы данных Parse, и после многих экспериментов, похоже, терпит неудачу при написании успешных модульных тестов. Я нашел несколько сообщений по тестированию асинхронного кода (Testing asynchronous call in unit test in iOS) и тестирования сетевых вызовов, но я еще не нашел способ тестирования вызовов на бэкэнд Parse с асинхронными обратными вызовами.Тестирование модулей Рамка анализа iOS

К примеру, кто-то может посоветовать, как я хотел бы проверить следующую строку кода:

[PFUser saveUser:userModelMock withBlock:^(BOOL success, NSError *error) { 

}]; 

Я использую OCMock и рамки XCTest.

Любая помощь будет высоко оценена.

* EDIT * Это то, что я до сих пор, но, кажется, терпит неудачу

- (void)testSaveUser { 
    id userModelMock = [OCMockObject niceMockForClass:[UserModel class]]; 
    id userControllerMock = [OCMockObject niceMockForClass:[self.userController class]]; 

    [[userModelMock expect] saveInBackgroundWithBlock:[OCMArg any]]; 
    [[userControllerMock stub] saveUser:userModelMock withBlock:[OCMArg any]]; 

    [userModelMock verify]; 
} 
+1

Что это такое, что вы пытаетесь проверить? PFUser, надеюсь, имеет свои собственные модульные тесты, поэтому ваши тесты должны рассчитывать на то, что он работает как рекламируемый, и вы можете просто высмеять вызов saveUser: withBlock :. Если вы хотите проверить, что ваш асинхронный обратный вызов работает должным образом, этот ответ может помочь: http://stackoverflow.com/a/20694495/449161 –

+0

Согласен, я бы предположил, что у Parse есть внутренние тесты - однако я подклассифицирую PFUser и создал контроллер, который обрабатывает все данные.Внутри контроллера у меня есть метод, который вызывает saveUser, поэтому в тесте мне нужно проверить saveUser: вызывается, когда я вызываю метод, который его обертывает. –

+0

Если вы тестируете, что контроллер вызывает этот метод из другого метода, просто используйте макет и «ожидает» для объекта PFUser и вызовите метод контроллера из вашего теста. Если вызов асинхронный, следуйте модели из сообщения, которое вы связали. Или я чего-то не хватает? –

ответ

4

Если вы не возражаете, чтобы использовать библиотеку третьей стороны, вы можете использовать следующий подход:

#import <XCTest/XCTest.h> 
#import <RXPromise/RXPromise.h> 
... 

@interface PFUserTests : XCTestCase 
@end 

@implementation PFUserTests 

// helper: 
- (RXPromise*) saveUser:(User*)user { 
    RXPromise* promise = [[RXPromise alloc] init]; 
    [PFUser saveUser:user withBlock:^(Bool success, NSError*error){ 
     if (success) { 
      [promise fulfillWithValue:@"OK"]; 
     } 
     else { 
      [promise rejectWithReason:error]; 
     } 
    }]; 
    return promise; 
} 


// tests: 

-(void) testSaveUser 
{ 
    User* user = ... ; 
    RXPromise* promise = [self saveUser:user]; 

    // set a timeout: 
    [promise setTimeout:5.0]; 

    [promise.thenOn(dispatch_get_main_queue(), ^id(id result) { 
     XCTAssertTrue([result isEqualToString:@"OK"], @""); 
     XCTAssertTrue(... , @""); 
     return nil; 
    }, ^id(NSError* error) { 
     // this may fail either due to a timeout or when saveUser fails: 
     XCTFail(@"Method failed with error: %@", error); 
     return nil; 
    }) runLoopWait]; // suspend the run loop, until after the promise gets resolved 

} 

несколько замечаний:

Вы можете запустить обработчик успех или неудача (которые зарегистрированы для PROMI se с оператором thenOn) на любой очередь, просто укажите очередь. Это не блокирует основной поток, хотя обработчик явно выполняется в основном потоке, а тест также запускается в основном потоке (благодаря механизму цикла запуска).

Метод runLoopWait будет вводить цикл выполнения и возвращаться только после того, как обещание будет разрешено. Это и эффективно, и эффективно.

Обещание принимает таймаут. Если истечет время ожидания, обещание будет «отклонено» (которое будет разрешено) с соответствующей ошибкой.

IMO, обещания - бесценный инструмент для решения общих проблем асинхронного программирования - а не только помощник для модульных тестов. (Смотри также: вики-статью Futures and promises

Пожалуйста, обратите внимание, что я автор RXPromise library, и, таким образом, я совершенно предвзято;.)

Есть и другие варианты реализации Promise для Objective-C, а также.

+0

Спасибо за это, я посмотрел, и RXPromise выглядит как действительно полезная инфраструктура, так что хорошо с этим справиться. Возможно, я не полностью объяснил себя; Я не хочу вызывать метод saveUser: withBlock: напрямую, поскольку он делает сетевой вызов, который, очевидно, не идеален для тестирования. Есть ли способ, которым я могу издеваться над этим? Еще раз спасибо. –

+0

Взгляните на [OCMock] (http://ocmock.org), например;) – CouchDeveloper

+0

Нет необходимости в сторонних библиотеках - вы можете использовать XCTest Expectation. Учебное пособие по NSHipster: http://nshipster.com/xctestcase/ –

1

Оказывается, это было связано с моим отсутствием понимания единицы измерения. После некоторых исследований на фиктивных объектов, заглушек и т.п. я придумал:

- (void)testSaveUser { 
    id userModelMock = [OCMockObject mockForClass:[UserModel class]]; 
    id userControllerMock = [OCMockObject partialMockForObject:self.userController]; 

    [[userModelMock expect] saveInBackgroundWithBlock:[OCMArg any]]; 
    [userControllerMock saveUser:userModelMock withBlock:[OCMArg any]]; 

    [userModelMock verify]; 
} 
+1

Да, похоже, изначально вы не называли метод, который хотите проверить! Я предполагаю, что 'self.userController' возвращает объект пользовательского контроллера, созданный вашим тестовым классом для каждого теста. Рад, что вы добиваетесь прогресса. –

+0

Просто сказать, что передача [OCMArg any] в качестве блока в методе, который вы хотите протестировать, не имеет смысла. Вместо этого передайте поддельный блок или нуль, в зависимости от того, что вы хотите проверить – e1985

1

С в https://github.com/hfossli/AGAsyncTestHelper/ макро WAIT_WHILE(<expression>, <time_limit>) вы можете написать

- (void)testSaveUser 
{ 
    __block BOOL saved = NO; 
    [PFUser saveUser:userModelMock withBlock:^(BOOL success, NSError *error) { 
     saved = YES; 
    }]; 
    WAIT_WHILE(!saved, 10.0, @"Failed to save user within 10 seconds timeframe); 
} 
Смежные вопросы