Насколько гранулярными должны быть тесты TDD?

18

Во время обучения TDD на основе медицинского программного обеспечения мы реализуем следующую историю: «Когда пользователь нажимает кнопку« Сохранить », система должна добавить пациента, добавить устройство и добавить записи данных устройства».

Окончательная реализация будет выглядеть примерно так:

if (_importDialog.Show() == ImportDialogResult.SaveButtonIsPressed)
{
   AddPatient();
   AddDevice();
   AddDeviceDataRecords();
}

У нас есть два способа реализовать это:

  1. Три теста, где каждый проверяет один метод (AddPatient, AddDevice, AddDeviceDataRecords) был вызван
  2. Один тест, который проверяет, все три метода были вызваны

В первом случае, если что-то не так с условием условия if, все три теста не пройдут. Но во втором случае, если тест не пройден, мы не уверены, что именно не так. Какой способ вы бы предпочли.

SiberianGuy
источник

Ответы:

8

Но во втором случае, если тест не пройден, мы не уверены, что именно не так.

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

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

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

Петер Тёрёк
источник
Не считаете ли вы разумным объединить эти три теста в один?
SiberianGuy
@Idsa, может быть разумным решением, хотя на практике я редко сталкиваюсь с таким рефакторингом. Опять же, я работаю с унаследованным кодом, где приоритеты разные: мы сосредоточены на увеличении охвата тестов существующим кодом и сохранении растущего количества поддерживаемых модульных тестов.
Петер Тёрёк
30

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

Unittest тестирует одну функциональную единицу с минимальным количеством зависимостей. В вашем случае может быть 4 юниттеста

  • AddPatient добавляет пациента (т.е. вызывает соответствующие функции базы данных)?
  • AddDevice добавляет устройство?
  • AddDeviceDataRecords добавляет записи?
  • выполняет unamend основную функцию в вашем примере, вызывая AddPatient, AddDevice и AddDeviceFunctions

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

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

когда пользователь вводит данные, нажимает ОК и ...

  • ... идет к списку пациентов, он должен увидеть нового пациента с указанным именем
  • ... идет в список устройств, он должен увидеть новое устройство
  • ... идет к деталям нового устройства, он должен увидеть новые записи данных

приемочные тесты предназначены для клиентов или для улучшения связи с ними.

Чтобы ответить на ваш вопрос «что бы вы предпочли»: что является для вас большей проблемой, ошибки и регрессия (=> больше юниттестов) или понимание и формализация общей картины (=> больше приемочных тестов)

keppla
источник
13

У нас есть два способа реализовать это:

Это неверно

Три теста, где каждый проверяет один метод (AddPatient, AddDevice, AddDeviceDataRecords) был вызван

Вы должны сделать это, чтобы убедиться, что это работает.

Один тест, который проверяет, все три метода были вызваны

Вы также должны сделать это, чтобы убедиться, что API работает.

Класс - как единое целое - должен быть полностью проверен. Каждый метод.

Вы можете начать с теста, который охватывает все три метода, но он мало о чем говорит.

Если тест не пройден, мы не уверены, что именно не так.

Верный. Вот почему вы тестируете все методы.

Вы должны проверить открытый интерфейс. Поскольку этот класс выполняет три плюс одно (даже если они связаны одним методом из-за пользовательской истории), вы должны протестировать все четыре вещи. Три низкоуровневых и один пучок.

С. Лотт
источник
2

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

Например, представьте, что добавление пациента в вашу систему требует вызова некоторых подпрограмм (дочерних функций):

  1. VerifyPatientQualification
  2. EnsureDoctorExistence
  3. CheckInsuranceHistory
  4. EnsureEmptyBed

Мы могли бы также написать модульный тест для каждой из этих функций.

Саид Нямати
источник
2

Одно простое правило, которому я следовал, - это назвать тест так, чтобы он точно описывал, что делает тест. Если название теста становится слишком сложным, это признак того, что тест, возможно, делает слишком много. Так, например, присвоение имени тесту того, что вы предлагаете в варианте 2, может выглядеть как PatientIsAddedDeviceIsAddedAndDeviceDataRecordsWhenSaved, который намного сложнее, чем три отдельных теста PatientIsAddedWhenSaved, DeviceIsAddedWhenSaved, DataRecordsWhenSaved. Я также думаю, что уроки, которые можно извлечь из BDD, довольно интересны, когда каждый тест действительно соответствует одному требованию, которое можно описать на естественном языке.

jpierson
источник