Является ли издевательства лучше, чем окурки? - PullRequest
14 голосов
/ 06 сентября 2008

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

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

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

Ответы [ 6 ]

11 голосов
/ 07 сентября 2008

Когда мантра звучит так: «Иди с самой простой вещью, которая может сработать».

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

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

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

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

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

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

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

И к этому ...

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

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

2 голосов
/ 21 мая 2009

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

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

2 голосов
/ 07 сентября 2008

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

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

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

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

2 голосов
/ 07 сентября 2008

Это зависит только от того, какой тип тестирования вы проводите. Если вы проводите тестирование на основе поведения, вам может потребоваться динамический макет, чтобы вы могли убедиться, что происходит какое-то взаимодействие с вашей зависимостью. Но если вы проводите тестирование на основе состояния, вы можете захотеть заглушку, чтобы проверить значения / 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
1 голос
/ 06 сентября 2008

Прочтите обсуждение этого вопроса Люком Кансом в этом блоге . Он ссылается на пост от Джея Филдса , в котором даже говорится, что использование [эквивалента ruby's / mocha's] stub_everything предпочтительнее, чтобы сделать тесты более устойчивыми. Процитируем последние слова Филдса: «Мокко позволяет определить макет так же легко, как определить заглушку, но это не значит, что вы всегда должны предпочитать макеты. На самом деле, я обычно предпочитаю заглушки и при необходимости использую макеты. «

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...