Юнит-тестируйте функцию, которая выполняет селектор, и дождитесь ее завершения.

У меня есть класс MyService, который наследует NSThread:

заголовок:

@interface MyService : NSThread {
  -(void) startMe;
  -(void) doTask;
  ...
}

реализация:

@implementation MyService
  -(void)startMe {
   [self start];
  }
  -(void) doTask {
    [self performSelector:@selector(checkData:) onThread:self withObject:nil waitUntilDone:YES];
  }

  -(void) checkData {
    ...
    // NOTE: dataChecked is an instance variable.
    dataChecked = YES;
  }
@end

Я хочу провести модульное тестирование вышеуказанного -(void)doTask и убедиться, что -(void)checkData действительно вызывается. Я использую библиотеку OCMock, чтобы частично имитировать MyService, и попробовал следующее:

-(void) testCheckData {
  // partial mock MyService
  id myService = [OCMockObject partialMockForObject:[MyService getInstance]];
  [myService startMe];

  // function to test
  [myService doTask];

  // I wait 2 seconds for checkData to finish its work
  dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
    dispatch_after(delayTime, dispatch_get_main_queue(), ^(void){
        // When running this test case, these code is never called & test passed successfully, WHY?
        NSLog(@"Check if checkData does its work");
        XCTAssertTrue([self isDataChecked]);
    });
}

Два вопроса:

Q1. Как вы видите выше, в моем тестовом примере я вызываю -(void)doTask и подожду 2 секунды, ожидаю, что -(void)checkData будет выполнено, затем подтверждаю результат, но при выполнении тестового примера , он не запускает блок в dispatch_after(...) и тест пройден успешно, ПОЧЕМУ он не запускает блок?

Вопрос 2. Как правильно выполнить модульное тестирование такой функции, как -(void)doTask, которая вызывает NSObject#performSelector:onThread:withObject:waitUntilDone:?


person Leem.fin    schedule 15.06.2016    source источник
comment
Вам нужно использовать XCTestExpectation и waitForExpectationsWithTimeout:, вот довольно хорошее объяснение этого bignerdranch.com/blog/asynchronous-testing-with-xcode-6   -  person sbarow    schedule 15.06.2016
comment
параметр тайм-аута в секундах или миллисекундах?   -  person Leem.fin    schedule 15.06.2016
comment
Это в секундах, если я правильно помню.   -  person sbarow    schedule 16.06.2016


Ответы (1)


Q1

Если вы не можете изменить doTask, чтобы сделать его пригодным для тестирования, вы должны использовать XCTestExpectation, чтобы дождаться завершения теста. Кроме того, вместо того, чтобы ждать заданное количество времени, вы должны использовать цикл занятости, проверяя переменную на каждой итерации. Вы можете установить тайм-аут, ожидая, что произойдет сбой через некоторое разумное время.

// setup expectation    
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    while (dataChecked == NO);
    // signal expectation
});

// wait for expectation

Вопрос 2

Если вам нужно знать реализацию метода, чтобы успешно протестировать его, это не лучший кандидат для модульного тестирования. Не должно иметь значения, как тестируемый метод вызывает другие методы. Имеет значение только то, выполняются ли побочные эффекты синхронно или асинхронно.

person Avi    schedule 15.06.2016
comment
Я сделал то, что вы предложили в Q1, но независимо от того, сколько секунд я установил, часть ожидания просто возвращается немедленно, не дожидаясь. Но если я установлю точку останова в части waitForExpectationsWithTimeout и нажму кнопку «Продолжить», она будет ждать. Есть идеи, почему? - person Leem.fin; 15.06.2016
comment
Я задал новый вопрос об этой проблеме, посмотрите здесь: stackoverflow.com/questions/37845892/ - person Leem.fin; 16.06.2016