Уровень обслуживания приложений: модульные тесты, интеграционные тесты или оба? - PullRequest
14 голосов
/ 13 июля 2011

В моем слое службы приложений есть несколько методов, которые делают такие вещи:

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 */
}

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

Должен ли уровень обслуживания приложений тестироваться до этого уровня детализации или достаточно интеграционных тестов?

Ответы [ 3 ]

12 голосов
/ 13 июля 2011

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

[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());
}

Нет необходимости проверять, чтобы были вызваны load и / или save. Вместо этого я бы просто убедился, что единственный способ, которым MyMockRepository вернет обновленный порядок, - это вызовы load и save.

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

9 голосов
/ 13 июля 2011

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

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

Предполагая, что у вас есть (интеграционный) тест для service.Execute(placeOrderOnHoldCommand), я не совсем уверен, добавит ли он значение для проверки, загружает ли служба заказ из хранилища ровно один раз. Но это может быть! Например, когда у вашего сервиса ранее была неприятная ошибка, которая приводила к попаданию в хранилище десять раз за один заказ, что приводило к проблемам с производительностью (просто компенсируя это). В этом случае я бы переименовал тест в PlaceOrderOnHold_LoadsOrderFromRepositoryExactlyOnce().

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

Примечания:

  1. Тесты, которые вы показываете, могут быть совершенно обоснованными и хорошо написанными.

  2. Похоже, что ваши методы тестовой последовательности вдохновлены тем, как в настоящее время реализуется метод Execute(...). Когда вы структурируете свой тест таким образом, вы можете привязать себя к конкретной реализации. Таким образом, тесты могут на самом деле усложнить изменение - убедитесь, что вы тестируете только важное внешнее поведение вашего класса.

4 голосов
/ 13 июля 2011

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

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

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

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

...