Разве единичное тестирование не подтверждает принцип DRY?

12

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

Так что тестирование с одним утверждением нарушает DRY?

И есть ли хорошее правило, которому нужно следовать, чтобы найти хороший баланс, как, например, проведение одного теста на метод ? *

* Я понимаю, что, вероятно, не существует универсального решения для этого, но есть ли рекомендуемый способ подойти к этому?

Корей Хинтон
источник
5
Вы можете извлечь код, который вы копируете в методы
Исмаил Бадави
@ IsmailBadawi звучит как хорошая идея. Я предполагаю, что эти методы должны возвращать экземпляр объекта класса, который я тестирую
Korey Hinton
1
Вы создаете что-то в данном состоянии для проверки? Это звучит как приспособление.
Дэйв Хиллиер,
@DaveHillier да, я выучил новое слово сегодня. Спасибо :)
Кори Хинтон
зависит от того, как вы интерпретируете 1 утверждение за тест, если вы имеете в виду один вызов Assert *, тогда да, если вы также хотите убедиться, что инварианты все еще выполняются (затем снова извлечете это в метод), или если есть несколько эффектов, которые вы можете просто ' t-тест в одном утверждении (или, если вы это сделали, тогда не будет понятно, почему это не удалось)
трещотка урод

Ответы:

14

Правильные модульные тесты имеют соглашение об именах, которое помогает вам сразу определить, что не удалось:

public void AddNewCustomer_CustomerExists_ThrowsException()

Вот почему у вас есть одно утверждение на тест, так что каждый метод (и его имя) соответствует условию, которое вы утверждаете.

Как вы правильно указали, каждый новый тест будет иметь похожий код установки. Как и в случае с любым другим кодом, вы можете преобразовать общий код в собственный метод, чтобы уменьшить или устранить дублирование и сделать ваш код более СУХИМЫМ. Некоторые среды тестирования специально разработаны для того, чтобы вы могли разместить этот установочный код в одном месте .

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

Роберт Харви
источник
Рефакторинг в единый метод звучит хорошо. Если бы мне нужен был экземпляр класса, чтобы он находился в определенном состоянии, я мог бы создать и вернуть новый экземпляр этого объекта в рефакторизованном методе или, как вы предлагаете, использовать методы, предоставляемые каркасом тестирования для начальной настройки теста.
Корей Хинтон
В заключение , я уверен, что смогу написать тесты на функциональность, которую я хотел бы иметь в коде, а не на функциональность, в которой он нуждался
joelb
5

Так что тестирование с одним утверждением нарушает DRY?

Нет, но это способствует нарушению.

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

Честно говоря, я никогда не был поклонником правила «одно утверждение для каждого теста» по причинам, которые вы описываете: это приводит к большому количеству шаблонного кода, который трудно читать, легко неправильно разбирать и трудно исправить, если вы будете проводить рефакторинг (что заставляет вас меньше заниматься рефакторингом).

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

Я рассматриваю это как вариант Принципа единой ответственности: должно быть только одно, что может привести к провалу теста, и в идеальном мире это изменение должно нарушить только один тест.

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

Telastyn
источник
1
Для тестов важнее быть DAMP, чем DRY (описательные и осмысленные фразы).
Йорг Миттаг
2

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

Используйте тестовое приспособление (хотя в терминологии XUnit набор тестов, установка и разборка - это фиксатор), то есть некоторые настройки или примеры, применимые ко всем вашим тестам.

Используйте методы, которые вы обычно используете для структурирования своего кода. Когда рефакторинг тесты обычный TDD Red-Green-Refactor не применяются, вместо них будут применяться, «Рефакторинг в красном». Это,

  1. сознательно сломать свой тест,
  2. сделать свой рефакторинг
  3. исправить свой тест

Таким образом, вы знаете, что тесты все еще дают положительные и отрицательные результаты.

Существует несколько стандартных форматов для тестов. Например, Arrange, Act, Assert или Given When, Then (BDD) . Рассмотрите возможность использования отдельной функции для каждого шага. Вы должны быть в состоянии вызвать функцию, чтобы уменьшить шаблон.

Дэйв Хиллиер
источник