Тестирование на необходимое поведение по сравнению с TDD

Вы попытались смотреть на html2pdf? домашняя страница http://html2pdf.seven49.net/Web/

существует также проект SourceForge ее.

Alan

5
задан Matt Curtis 15 September 2009 в 14:07
поделиться

4 ответа

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

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

Для меня большой сдвиг парадигмы произошел, когда я начал писать тесты, даже не задумываясь о реализации. Сначала меня удивило то, что стало намного проще создавать «экстремальные» тестовые примеры. Затем я понял, что улучшенный интерфейс, в свою очередь, помог сформировать стоящую за ним реализацию. В результате мой код в настоящее время не делает ничего большего, чем предоставляет интерфейс, что эффективно снижает потребность в большинстве тестов «реализации».

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

3
ответ дан 14 December 2019 в 19:19
поделиться

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

Принцип DRY применим как к тестируемому, так и к производственному коду. Это часто может быть хорошим ориентиром при написании тестового кода. Цель должна состоять в том, чтобы все «случайное» поведение, которое вы указываете на этом пути, было изолировано, чтобы их использовали только несколько тестов из всего набора тестов. Таким образом, если вам нужно реорганизовать это поведение, вам нужно будет изменить только несколько тестов, а не большую часть всего набора тестов.

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

Вот пример того, что я имею в виду. Предположим, что у вас есть какая-то реализация MVC, в которой контроллер должен возвращать представление. Предположим, что у нас есть такой метод в BookController:

public View DisplayBookDetails(int bookId)

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

Определите дополнительный интерфейс IBookMapper и вставьте его в BookController в дополнение к IBookRepository. Тогда реализация метода могла бы быть примерно такой:

public View DisplayBookDetails(int bookId)
{
    return this.mapper.Map(this.repository.GetBook(bookId);
}

Очевидно, это слишком упрощенный пример,

1
ответ дан 14 December 2019 в 19:19
поделиться

«А как насчет разделения их в отдельный тест? набор? "

Что бы вы сделали с этим отдельным набором?

Вот типичный вариант использования.

  1. Вы написали несколько тестов, детали реализации которых им не следовало тестировать.

  2. Вы вычленяете эти тесты за скобки. основного пакета в отдельный пакет.

  3. Кто-то изменяет реализацию.

  4. Ваш пакет реализации теперь не работает (как и должно быть).

Что теперь?

  • Исправить тесты реализации? Думаю, нет. Смысл в том, чтобы не тестировать реализацию, потому что это приводит к большому количеству работ по обслуживанию.

  • Есть ли тесты, которые могут дать сбой, но в целом выполнение unittest все еще считается хорошим? Если тесты терпят неудачу, но неудача не имеет значения, что это вообще значит? [Прочтите этот вопрос для примера: Некритические сбои модульного теста Игнорируемый или нерелевантный тест просто дорогостоящий.

Вы должны отказаться от них.

Сэкономьте себе время и избавьтесь от неприятностей, отказавшись от них сейчас а не тогда, когда они терпят неудачу.

1
ответ дан 14 December 2019 в 19:19
поделиться

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

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

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

Я работаю, что мы делаем, это просто исправляем такие тесты, когда мы натыкаемся на них. То, как мы их исправим, зависит от типа выполняемого случайного теста.

  • наиболее распространенный такой тест - это, вероятно, тот случай, когда результаты тестирования происходят в определенном порядке, игнорируя этот порядок, на самом деле не гарантируется. Легкое исправление достаточно простое: отсортируйте результат и ожидаемый результат. Для более сложных структур используйте какой-нибудь компаратор, который игнорирует такого рода различия.

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

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

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

0
ответ дан 14 December 2019 в 19:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: