Как бы я провел модульное тестирование?

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

Общее описание: Пользователь должен иметь возможность удалить план. С планом связаны задачи, их также необходимо удалить (если они еще не выполнены).

Псевдокод того, как должен вести себя алгоритм:

   PlanController.DeletePlan(plan)
     =>
     PlanDbRepository.DeletePlan()
      ForEach Task t in plan.Tasks
          If t.Status = Status.Open Then
            TaskDbRepository.DeleteTask(t)
          End If
      End ForEach

Теперь, насколько я понимаю, модульные тесты не должны касаться базы данных или вообще требуют доступа к каким-либо внешним системам, поэтому я предполагаю, что у меня есть два варианта:

1) Смоделируйте вызовы репозитория и проверьте, вызывались ли они соответствующее количество раз в качестве утверждений

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

В обоих подходах возникает большой вопрос: что именно я здесь тестирую? Какую ДОПОЛНИТЕЛЬНУЮ ценность мне дадут такие тесты?

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


person Sam    schedule 08.12.2010    source источник


Ответы (7)


Вы должны смоделировать репозиторий, а затем создать фиктивный план в своем модульном тесте, содержащем как открытые, так и закрытые задачи. Затем вызовите фактический метод, передающий этот план, и в конце убедитесь, что метод DeleteTask был вызван с правильными аргументами (задачи только со статусом = Открыто). Таким образом, вы гарантируете, что только открытые задачи, связанные с этим планом, будут удалены вашим методом. Также не забудьте (возможно, в отдельном модульном тесте) убедиться, что сам план был удален, утверждая, что метод DeletePlan был вызван для объекта, который вы передаете.

person Darin Dimitrov    schedule 08.12.2010

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

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

person Gerrie Schenck    schedule 08.12.2010

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

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

person Meff    schedule 08.12.2010

Дополнительная ценность, предоставляемая вашими тестами, заключается в проверке того, что ваш код выполняет правильные действия (в этом случае удалите план, удалите все открытые задачи, связанные с планом, и оставьте все закрытые задачи, связанные с планом).

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

Вот некоторые тесты, которые вы можете написать:
Вызывает ли удаление пустого плана только DeletePlan?
Вызывает ли удаление плана с двумя открытыми задачами DeleteTask для обеих задач?
Не вызывает ли удаление плана с двумя закрытыми задачами DeleteTask вообще?
Вызывает ли удаление плана с одной открытой и одной закрытой задачей DeleteTask один раз для нужной задачи?

Изменить: я бы использовал ответ Дарина как способ сделать это.

person Jackson Pope    schedule 08.12.2010

Интересно, я считаю, что модульное тестирование помогает сосредоточиться на спецификациях. В связи с этим позвольте мне задать этот вопрос...

Если у меня есть план с 3 задачами:

Plan1 {
 Task1: completed
 Task2: todo
 Task3: todo
}

и я призываю удалить их, что должно произойти с Планом?

Plan1 : ?
Task1: not deleted
Task2: deleted
Task3: deleted

Удален ли план1, потеряв задачу1? или он иным образом помечен как удаленный?.

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

Что касается того, как тестировать, я бы вообще не предлагал моки. Я бы рассмотрел метод из двух частей. Первый будет выглядеть так:

public void DeletePlan(Plan p)
{ 
  var objectsToDelete = GetDeletedPlanObjects(p);
  DeleteObjects(objectsToDelete);
} 

И я бы не стал тестировать этот метод. Я бы протестировал метод GetDeletedPlanObjects, который в любом случае не коснется базы данных и позволит вам отправлять сценарии, подобные описанной выше ситуации.... которую я затем утверждаю с помощью www.approvaltests.com, но это уже другая история: - )

Удачных испытаний, Ллевеллин

person llewellyn falco    schedule 23.04.2013

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

Рефакторинг бизнес-логики в «TaskRemovalStrategy», если вы хотите протестировать бизнес-логику для этого и оставить детали реализации удаления на усмотрение самого класса.

person JDT    schedule 08.12.2010

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

Например, вы можете написать тест -

void DeletePlanTest()
{
    PlanRepository repo = new PlanDbRepository("connection string");
    repo.CreateNewPlan(); // create plan and populate with tasks
    AssertIsTrue(repo.Plan.OpenTasks.Count == 2); // check tasks are in open state
    repo.DeletePlan();
    AssertIsTrue(repo.Plan.OpenTasks.Count == 0);
}

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

Значение такого теста заключается в том, выполняется ли тест для PlanDbRepository или MockRepository, он все равно проверит правильность поведения. Поэтому, когда вы меняете код репозитория или даже схему базы данных, вы можете запустить тесты, чтобы убедиться, что ничего не сломано.

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

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

person Unmesh Kondolikar    schedule 08.12.2010