Когда я должен смеяться?


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

4   97  

4 ответа:

модульный тест должен тестировать один кодовый путь с помощью одного метода. Когда выполнение метода проходит вне этого метода, в другой объект и обратно, у вас есть зависимость.

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

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

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

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

TL; DR: издевайтесь над каждой зависимостью, которую касается ваш модульный тест.

макетные объекты полезны, когда вы хотите тест взаимодействий между тестируемым классом и определенным интерфейсом.

например, мы хотим проверить, что метод sendInvitations(MailServer mailServer) звонки MailServer.createMessage() ровно один раз, а также звонки MailServer.sendMessage(m) ровно один раз, и никакие другие методы не вызываются на MailServer интерфейс. Это когда мы можем использовать фиктивные объекты.

с фиктивными объектами, вместо передачи реального MailServerImpl, или тест TestMailServer, мы можем пройти макет реализация MailServer интерфейс. Прежде чем мы пройдем макет MailServer, мы "тренируем" его, чтобы он знал, какие вызовы метода ожидать и какие возвращаемые значения возвращать. В конце концов, макет объекта утверждает, что все ожидаемые методы были вызваны так, как ожидалось.

это звучит хорошо в теории, но есть и некоторые недостатки.

Mock недостатки

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

вот пример в псевдокоде. Предположим, что мы создали MySorter класс и мы хотим проверить это:

// the correct way of testing
testSort() {
    testList = [1, 7, 3, 8, 2] 
    MySorter.sort(testList)

    assert testList equals [1, 2, 3, 7, 8]
}


// incorrect, testing implementation
testSort() {
    testList = [1, 7, 3, 8, 2] 
    MySorter.sort(testList)

    assert that compare(1, 2) was called once 
    assert that compare(1, 3) was not called 
    assert that compare(2, 3) was called once 
    ....
}

(в этом примере мы предполагаем, что это не конкретный алгоритм сортировки, такой как быстрая сортировка, который мы хотим проверить; в этом случае последний тест будет действительно действительным.)

в таком экстремальном примере очевидно, почему последний пример неверен. Когда мы меняем реализацию MySorter, первый тест делает большую работу, чтобы убедиться, что мы все еще сортируем правильно, в чем и заключается весь смысл тестов - они позволяют нам безопасно изменять код. На с другой стороны, последний тест всегда ломается, и это активно вредно; это мешает рефакторингу.

издевается как окурки

макетные фреймворки часто позволяют также менее строгое использование, где нам не нужно точно указывать, сколько раз методы должны быть вызваны и какие параметры ожидаются; они позволяют создавать макетные объекты, которые используются как заглушки.

предположим, что у нас есть метод sendInvitations(PdfFormatter pdfFormatter, MailServer mailServer) что мы хотим протестировать. Этот

правило:

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

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

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

отличный подкаст на эту тему можно найти здесь