Являются ли насмешки лучше, чем окурки?

Некоторое время назад я читал статью Mocks Are not Stubs от Мартина Фаулера, и я должен признать, что я немного боюсь внешнего зависимостей в отношении дополнительной сложности, поэтому я хотел бы спросить:

Каков наилучший метод использования при модульном тестировании?

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

Ответы

Ответ 1

По мере того, как мантра идет "Иди с самой простой вещью, которая может работать".

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

Избегайте использования mocks всегда, потому что они делают тесты хрупкими. Ваши тесты теперь имеют сложные знания о методах, вызванных реализацией, если измененный интерфейс или ваша реализация меняются... ваши тесты прерываются. Это плохо, потому что вы потратите дополнительное время, чтобы ваши тесты выполнялись вместо того, чтобы просто запускать ваш SUT. Тесты не должны быть ненадлежащим образом близки к реализации.
Поэтому используйте свое лучшее суждение. Я предпочитаю макеты, когда это поможет мне сэкономить на записи - обновление поддельного класса с помощью n → 3 методов.

Обновить Эпилог/Обсуждение:
(Спасибо Торану Биллапсу, например, за маховик-тест. См. Ниже)
Привет, Дуг, я думаю, что мы перешли в другую священную войну - Classic TDDers против Mockist TDDers. Я думаю, что я принадлежу первому.

  • Если я нахожусь на test # 101 Test_ExportProductList, и я считаю, что мне нужно добавить новый параметр в IProductService.GetProducts(). Я делаю это, чтобы этот тест был зеленым. Я использую инструмент рефакторинга для обновления всех других ссылок. Теперь я нахожу, что все мокстинские тесты, вызывающие этот член, теперь взрываются. Затем мне нужно вернуться и обновить все эти тесты - пустую трату времени. Почему DoPopulateProductsListOnViewLoadWhenPostBackIsFalse не удалось? Было ли это из-за нарушения кода? Скорее тесты нарушены. Я одобряю один отказ теста = 1 место для исправления. Отвратительная частота идет вразрез с этим. Будут ли окурки лучше? Если бы у меня был fake_class.GetProducts().. уверен, что одно место вместо операции дробовика изменилось бы на несколько вызовов Expect. В конце концов, это вопрос стиля. Если бы у вас был общий способ утилиты MockHelper.SetupExpectForGetProducts() - этого было бы достаточно... но вы увидите, что это необычно.
  • Если вы поместите белую полосу на имя теста, тест трудно прочитать. Много кода сантехники для фальшивой рамки скрывает фактический тест.
  • требует, чтобы вы узнали этот особый аромат насмешливой структуры.

Ответ 2

Обычно я предпочитаю использовать mocks из-за ожиданий. Когда вы вызываете метод на заглушке, который возвращает значение, он обычно возвращает вам значение. Но когда вы вызываете метод на макет, он не только возвращает значение, но также обеспечивает ожидание того, что вы установили, что метод был даже вызван в первую очередь. Другими словами, если вы настроили ожидание, а затем не вызываете этот метод, генерируется исключение. Когда вы устанавливаете ожидание, вы, по сути, говорите: "Если этот метод не вызван, что-то пошло не так". И наоборот, если вы вызываете метод на макет и не задаете ожидания, он будет генерировать исключение, в основном говоря "Эй, что вы делаете, называя этот метод, когда вы этого не ожидали".

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

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

И к этому...

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

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

Ответ 3

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

Например, в приведенном ниже тесте вы заметите, что я закрываю представление, чтобы проверить, установлено ли значение свойства (тестирование на основе состояния). Затем я создаю динамический макет класса службы, поэтому я могу убедиться, что во время теста вызывается определенный метод (тестирование на основе взаимодействия/поведения).

<TestMethod()> _
Public Sub Should_Populate_Products_List_OnViewLoad_When_PostBack_Is_False()
    mMockery = New MockRepository()
    mView = DirectCast(mMockery.Stub(Of IProductView)(), IProductView)
    mProductService = DirectCast(mMockery.DynamicMock(Of IProductService)(), IProductService)
    mPresenter = New ProductPresenter(mView, mProductService)
    Dim ProductList As New List(Of Product)()
    ProductList.Add(New Product())
    Using mMockery.Record()
        SetupResult.For(mView.PageIsPostBack).Return(False)
        Expect.Call(mProductService.GetProducts()).Return(ProductList).Repeat.Once()
    End Using
    Using mMockery.Playback()
        mPresenter.OnViewLoad()
    End Using
    'Verify that we hit the service dependency during the method when postback is false
    Assert.AreEqual(1, mView.Products.Count)
    mMockery.VerifyAll()
End Sub

Ответ 4

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

  • Если вызов внешнего кода является частью вашего ожидаемого кода (наружу), это должно быть проверено. Используйте макет.
  • Если вызов - это действительно деталь реализации, которую внешний мир не интересует, предпочитайте заглушки. Однако:
  • Если вы обеспокоены тем, что последующие реализации проверенного кода могут случайно обойти ваши заглушки, и вы хотите заметить, что это происходит, используйте макет. Вы связываете свой тест с вашим кодом, но это ради того, чтобы заметить, что ваш заглушка больше не достаточен, и ваш тест нуждается в повторной работе.

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

И снова это запах кода и последнее средство. Если вы обнаружите, что вам нужно делать это часто, попробуйте переосмыслить то, как вы пишете свои тесты.

Ответ 5

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

Для более длительного обсуждения рассмотрите вопрос о http://www.mockobjects.com/book

Ответ 6

Прочитайте обсуждение Luke Kanies именно этого вопроса в этом сообщении в блоге. Он ссылается сообщение от Jay Fields, которое даже предполагает, что использование [эквивалент Ruby's/mocha's] stub_everything предпочтительнее, чтобы тесты были более надежными. Чтобы процитировать заключительные слова Fields: "Mocha позволяет легко определить макет, так как он определяет заглушку, но это не значит, что вы всегда должны отдавать предпочтение издевательствам. На самом деле я обычно предпочитаю заглушки и при необходимости использовать макеты."