#objective-c #unit-testing #ocunit
#objective-c #модульное тестирование #ocunit
Вопрос:
Я знаю, как решить проблемы с EXC_BAD_ACCESS, но я не уверен, как выполнить модульный тест для этого. Есть ли способ зафиксировать EXC_BAD_ACCESS в коде вместо простого сбоя?
Вот почему я спрашиваю: я написал библиотеку, которая интенсивно использует блоки, подобные этому:
- (void)doSomething:(void (^)())myBlock;
В моей реализации doSomething:
я собираюсь в конечном итоге запустить блок, вот так:
myBlock();
Если вызывающий объект передает nil для блока, то произойдет сбой с EXC_BAD_ACCESS
, поэтому решение состоит в том, чтобы проверить, существует ли блок, вот так:
if (myBlock) {
myBlock();
}
Эту нулевую проверку довольно легко забыть, поэтому я хотел бы найти способ написать модульный тест, который завершается неудачей при сбое. Я полагаю, что сбой можно рассматривать как сбой теста, но я думаю, что другим, пытающимся запустить тесты, было бы приятнее видеть приятное сообщение об ошибке, а не сбой. Есть идеи?
Ответ №1:
Я думаю, вам нужно будет запустить тест в подпроцессе; затем вы можете допустить сбой подпроцесса, проверить наличие этого сбоя и аккуратно завершить тест, если это произойдет.
Работа с одноэлементным тестовым кодом Питера Хоси.
- (void) runTestInSubprocess:(SEL)testCmd {
pid_t pid = fork();
// The return value of fork is 0 in the child process, and it is
// the id of the child process in the parent process.
if (pid == 0) {
// Child process: run test
// isInSubprocess is an ivar of your test case class
isInSubprocess = YES;
[self performSelector:testCmd];
exit(0);
} else {
// Parent process: wait for child process to end, check
// its status
int status;
waitpid(pid, amp;status, /*options*/ 0);
// This was a crash; fail the test
STAssertFalse(WIFSIGNALED(status), @"Test %@ crashed due to signal %d", NSStringFromSelector(testCmd), WTERMSIG(status));
}
}
Затем каждый тест будет выполняться сам по себе в подпроцессе следующим образом:
- (void) testSomething {
if (!isInSubprocess) {
// Hand off this test's selector to be run in a subprocess
[self runTestInSubprocess:_cmd];
return;
}
// Put actual test code here
STAssertEquals(1, 1, @"Something wrong with the universe.");
}
Возможно, вам потребуется это настроить; я это не тестировал.
Комментарии:
1. Это очень интересно. При первой попытке тест завершается неудачей, что бы я ни делал, но проект ориентирован на статическую библиотеку Cocoa Touch, а выполнение тестов запускает симулятор iPhone. Я не думаю, что fork () будет работать там, поэтому я собираюсь попробовать это также в качестве целевого объекта Mac / Cocoa и посмотреть, что получится. Спасибо!
2. О, да, я не знаю,
fork()
доступно ли это вообще на iOS, извините. У меня тоже возникли проблемы с тем, чтобы заставить это работать полностью правильно; Я обновлю, если что-нибудь выясню.3. @greenisus У вас была возможность успешно решить это на iOS? Я также ищу способ сделать тест неудачным при возникновении сбоя, особенно в Swift.
Ответ №2:
Я бы предложил использовать один из макросов утверждения, найденных в Руководстве по программированию утверждений и протоколирования
Итак, вы могли бы сделать что-то вроде:
NSAssert(myBlock != nil, @"myBlock must not be nil")
Это обеспечивает выполнение предварительных условий, которые должны быть выполнены, прежде чем метод продолжит выполнение. Это также приводит к аварийному завершению работы приложения и укажет вам причину, отличную от EXEC_BAD_ACCESS.
Комментарии:
1. На самом деле это не помогает в модульном тестировании; greenisus с таким же успехом мог бы включить
if(myBlock){ myBlock() };
проверку, которую он упомянул в вопросе. Проблема, если я не совсем неправильно понял, заключается в том, как написать модульный тест, который обнаруживает, что он забывает проверить блок в тестируемом коде.2. Джош прав. Я вполне мог бы использовать это утверждение, но что я действительно пытаюсь проверить, так это то, что мои методы не аварийно завершают работу, когда я передаю им блок nil, так что не имеет значения, равен MyBlock nil или нет.
3. Используя утверждение, вы гарантируете, что
myBlock
значение не равно нулю. Если бы вы тестировали этот метод с помощью модульного теста, тест завершился бы неудачей из-за сбоя утверждения.4. Это не то, о чем я спрашиваю. Я хочу, чтобы nil было приемлемым значением для MyBlock, поэтому я ищу тест, который гарантирует, что передача nil не приведет к сбою.
5. Понял. Я этого не осознавал.