Уровень обслуживания приложений: модульные тесты, интеграционные тесты или оба?
У меня есть куча методов на моем уровне сервисов приложений, которые делают такие вещи:
public void Execute(PlaceOrderOnHoldCommand command)
{
var order = _repository.Load(command.OrderId);
order.PlaceOnHold();
_repository.Save(order);
}
И в настоящее время у меня есть куча модульных тестов:
[Test]
public void PlaceOrderOnHold_LoadsOrderFromRepository()
{
var repository = new Mock<IOrderRepository>();
const int orderId = 1;
var order = new Mock<IOrder>();
repository.Setup(r => r.Load(orderId)).Returns(order.Object);
var command = new PlaceOrderOnHoldCommand(orderId);
var service = new OrderService(repository.Object);
service.Execute(command);
repository.Verify(r => r.Load(It.Is<int>(x => x == orderId)), Times.Exactly(1));
}
[Test]
public void PlaceOrderOnHold_CallsPlaceOnHold()
{
/* blah blah */
}
[Test]
public void PlaceOrderOnHold_SavesOrderToRepository()
{
/* blah blah */
}
Кажется спорным, добавляют ли эти модульные тесты значение, которое стоит усилий. Я совершенно уверен, что уровень сервиса приложений должен быть протестирован на интеграцию.
Если уровень сервиса приложения должен быть протестирован на этот уровень детализации или достаточны тесты интеграции?
Ответы
Ответ 1
именно: это спорно! Это действительно хорошо, что вы взвешиваете расходы/усилие написания и поддержания вашего теста против ценности, которую оно принесет вам, - и это именно то внимание, которое вы должны сделать для каждого теста, который вы пишете. Часто я вижу тесты, написанные для тестирования и тем самым добавляя балласт к базе кода.
Как правило, я обычно принимаю, что мне нужен полный интеграционный тест для каждого важного успешного сценария/варианта использования. Другие тесты, которые я напишу, предназначены для частей кода, которые могут сломаться с будущими изменениями или сломаться в прошлом. И это определенно не весь код. Это означает, что ваше мнение и понимание системы и требований вступают в игру.
Предполагая, что у вас есть (интеграционный) тест для service.Execute(placeOrderOnHoldCommand)
, я не уверен, добавляет ли он значение для проверки, если служба загружает заказ из репозитория ровно один раз. Но это может быть! Например, когда у вашего сервиса ранее была неприятная ошибка, которая попадала бы в репозиторий десять раз за один заказ, вызывая проблемы с производительностью (просто создавая ее). В этом случае я переименовал бы тест на PlaceOrderOnHold_LoadsOrderFromRepositoryExactlyOnce()
.
Поэтому для каждого теста вы должны сами решить... надеюсь, что это поможет.
Примечания:
-
Те тесты, которые вы показываете, могут быть абсолютно корректными и выглядеть хорошо написанными.
-
Ваши методы тестовой последовательности, похоже, вдохновлены тем, как метод Execute(...)
в настоящее время реализован. Когда вы структурируете свой тест таким образом, возможно, вы привязываетесь к конкретной реализации. Таким образом, тесты могут существенно затруднить изменение - убедитесь, что вы только проверяете важное внешнее поведение вашего класса.
Ответ 2
Я бы написал unit test, несмотря на то, что также был интеграционный тест. Тем не менее, я, скорее всего, сделаю тест намного проще, устранив насмешливую структуру, нарисуя мой собственный простой макет, а затем объединяя все эти тесты, чтобы проверить, что порядок в макетном репозитории приостановлен.
[Test]
public void PlaceOrderOnHold_LoadsOrderFromRepository()
{
const int orderId = 1;
var repository = new MyMockRepository();
repository.save(new MyMockOrder(orderId));
var command = new PlaceOrderOnHoldCommand(orderId);
var service = new OrderService(repository);
service.Execute(command);
Assert.IsTrue(repository.getOrder(orderId).isOnHold());
}
Нет необходимости проверять, чтобы вызывать загрузку и/или сохранение. Вместо этого я просто удостоверился, что единственный способ вернуть MyMockRepository обновленный порядок - это вызвать загрузку и сохранение.
Такое упрощение является одной из причин, по которым я обычно не использую насмешливые фреймворки. Мне кажется, что вы намного лучше контролируете свои тесты и гораздо легче записываете их, если вы пишете свои собственные макеты.
Ответ 3
Обычно я пишу один интеграционный тест основного сценария. По первому сценарию я подразумеваю успешный путь всего тестируемого кода. Затем я пишу модульные тесты всех других сценариев, таких как проверка всех случаев в коммутаторе, проверка исключения и т.д.
Я думаю, что важно иметь и то и другое, и да, можно протестировать все это только с помощью интеграционных тестов, но это делает ваши тесты длительными и сложными для отладки. В среднем я думаю, что у меня 10 тестов на интеграцию.
Я не пытаюсь тестировать методы с помощью однострочных, если в этой строке не происходит что-то вроде логики.
Обновление: просто, чтобы понять, почему я занимаюсь разработкой, основанной на тестах, я всегда сначала пишу модульные тесты и обычно делаю тест интеграции в конце.