Какова цель макет объектов?

167

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

agentbanks217
источник
12
Это инструмент для массового переоснащения вещей с гибкостью, которая вам не нужна для решения поставленной задачи.
2010 года
2
Возможный дубликат Что такое издевательство?
nawfal

Ответы:

361

Поскольку вы говорите, что вы новичок в модульном тестировании, и попросили фиктивные объекты в «терминах непрофессионала», я попробую пример неспециалистов.

Модульное тестирование

Представьте себе модульное тестирование для этой системы:

cook <- waiter <- customer

Как правило, легко представить тестирование низкоуровневого компонента, такого как cook:

cook <- test driver

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

Труднее протестировать средний компонент, такой как официант, который использует поведение других компонентов. Наивный тестировщик может протестировать компонент официанта так же, как мы тестировали компонент готовки:

cook <- waiter <- test driver

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

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

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

    -----------------------
   |                       |
   v                       |
test cook <- waiter <- test driver

Здесь, тестовый повар "в сговоре" с тестовым водителем. В идеале, тестируемая система разработана таким образом , что тест повар может быть легко замещен ( впрыскивается ) для работы с официанта без изменения коды производства (например , без изменения коды официанта).

Макет объектов

Теперь тест-кулинар (test double) может быть реализован разными способами:

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

См. Статью Фаулера для получения более подробной информации о подделках, окурках, фиктивных и манекенах , но сейчас давайте сосредоточимся на фальшивом кулинаре.

    -----------------------
   |                       |
   v                       |
mock cook <- waiter <- test driver

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

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

Разговор о ложном тесте может выглядеть так:

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

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

тест-водитель : ТЕСТ УСПЕШЕН!

Но так как наш официант новичок, вот что может произойти:

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

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

Водитель-испытатель отмечает проблему: ТЕСТ НЕ ПРОВЕРЕН! - официант изменил порядок

или

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

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

Водитель-испытатель замечает неожиданный картофель фри: ТЕСТ НЕ ПРОВЕРЕН! официант вернул неправильное блюдо

Может быть трудно ясно увидеть разницу между фиктивными объектами и заглушками без контрастирующего примера на основе заглушки, но этот ответ уже слишком длинный :-)

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

Берт F
источник
12
Это отличное объяснение, но разве вы не в какой-то степени тестируете реализацию официанта? В вашем случае это, вероятно, нормально, потому что вы проверяете, что он использует правильный API, но что, если есть разные способы сделать это, и официант может выбрать один или другой? Я думал, что целью модульного тестирования является тестирование API, а не реализации. (Это вопрос, который я всегда задаю себе, когда читаю о насмешках.)
davidtbernal
8
Спасибо. Я не могу сказать, тестируем ли мы «реализацию», не видя (или не определяя) спецификацию для официанта. Вы можете предположить, что официанту разрешено самим готовить блюдо или выполнять заказ по улице, но я предполагаю, что в спецификации официанта входит использование предполагаемого шеф-повара - в конце концов, производственный шеф-повар - дорогой повар-гурман, и мы " Я бы предпочел, чтобы наш официант использовал его. Без этой спецификации, я думаю, я должен был бы прийти к выводу, что вы правы - официант может выполнить заказ так, как он хочет, чтобы он был «правильным». OTOH, без спецификации тест не имеет смысла. [Продолжение ...]
Берт F
8
Тем не менее, вы делаете замечательный вывод, который ведет к фантастической теме тестирования «белого ящика» и «черного ящика». Я не думаю, что в отрасли существует консенсус, согласно которому модульное тестирование должно быть «черным ящиком», а не «белым ящиком» («тестируйте API, а не реализацию»). Я думаю, что лучшим модульным тестированием, вероятно, должна быть комбинация этих двух факторов, чтобы сбалансировать хрупкость теста с охватом кода и полнотой тестового примера.
Берт F
1
Этот ответ, по моему мнению, недостаточно технический. Я хочу знать, почему я должен использовать фиктивный объект, когда я могу использовать реальные объекты.
Никлас Р.
1
Отличное объяснение !! Спасибо!! @BertF
Бхарат Мурали
28

Mock Object - это объект, который заменяет реальный объект. В объектно-ориентированном программировании фиктивные объекты - это симулированные объекты, которые имитируют поведение реальных объектов контролируемыми способами.

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

http://en.wikipedia.org/wiki/Mock_object

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

Слово «макет» иногда ошибочно используется как «заглушка». Различия между двумя словами описаны здесь. По существу, макет - это объект-заглушка, который также включает ожидания (то есть «утверждения») для правильного поведения тестируемого объекта / метода.

Например:

class OrderInteractionTester...
  public void testOrderSendsMailIfUnfilled() {
    Order order = new Order(TALISKER, 51);
    Mock warehouse = mock(Warehouse.class);
    Mock mailer = mock(MailService.class);
    order.setMailer((MailService) mailer.proxy());

    mailer.expects(once()).method("send");
    warehouse.expects(once()).method("hasInventory")
      .withAnyArguments()
      .will(returnValue(false));

    order.fill((Warehouse) warehouse.proxy());
  }
}

Обратите внимание, что объекты warehouseи mailermock запрограммированы с ожидаемыми результатами.

Роберт Харви
источник
2
Определение, которое вы дали, не в последнюю очередь отличается от «объекта-заглушки» и поэтому не объясняет, что такое фиктивный объект.
Брент Ариас
Еще одно исправление: «слово« Mock »иногда ошибочно используется как« заглушка »».
Брент Ариас
@Myst: использование двух слов не является универсальным; это варьируется среди авторов. Фаулер так говорит, и статья в Википедии так говорит. Тем не менее, не стесняйтесь редактировать изменения и удалить свое отрицательное голосование. :)
Роберт Харви
1
Я согласен с Робертом: использование слова «макет» имеет тенденцию варьироваться в зависимости от отрасли, но есть соответствии с моим опытом не существует определенного определения, за исключением того, что обычно оно НЕ является фактически проверяемым объектом, скорее оно существует для облегчения тестирования в тех случаях, когда используется фактическое объект или все его части будут очень неудобными и малозначительными.
mkelley33
15

Поддельные объекты - это моделируемые объекты, которые имитируют поведение реальных. Обычно вы пишете фиктивный объект, если:

  • Реальный объект слишком сложен, чтобы включить его в модульное тестирование (например, при сетевом взаимодействии вы можете иметь фиктивный объект, имитирующий другого партнера)
  • Результат вашего объекта не является детерминированным
  • Реальный объект еще не доступен
Дани Крико
источник
12

Объект Mock - это один из видов Test Double . Вы используете mockobjects для тестирования и проверки протокола / взаимодействия тестируемого класса с другими классами.

Как правило, вы будете в некотором роде «программными» или «записывать» ожидания: вызовы методов, которые вы ожидаете, что ваш класс сделает с базовым объектом.

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

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

Таким образом, в псевдо-языке с псевдо-фиктивной библиотекой у нас будет что-то вроде:

Widget sampleWidget = new Widget();
WidgetDao mock = createMock(WidgetDao.class);
WidgetService svc = new WidgetService(mock);

// record expected calls on the dao
expect(mock.getById(id)).andReturn(sampleWidget);   
expect(mock.save(sampleWidget);

// turn the dao in replay mode
replay(mock);

svc.updateWidgetPrice(id,newPrice);

verify(mock);    // verify the expected calls were made
assertEquals(newPrice,sampleWidget.getPrice());

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

Питер Тиллеманс
источник
11

Я настоятельно рекомендую отличную статью Мартина Фаулера, в которой объясняется, что такое макеты и чем они отличаются от заглушек.

Адам Быртек
источник
10
Не совсем для начинающих, не так ли?
Роберт Харви
@ Роберт Харви: Может быть, в любом случае приятно видеть, что это помогло уточнить ваш ответ :)
Адам Биртек
Статьи Мартина Фаулера написаны в духе RFC: сухо и холодно.
Revo
9

При модульном тестировании какой-либо части компьютерной программы в идеале вы хотите протестировать только поведение этой конкретной части.

Например, посмотрите на псевдокод ниже из воображаемого фрагмента программы, которая использует другую программу для вызова функции печати чего-либо:

If theUserIsFred then
    Call Printer(HelloFred)
Else
   Call Printer(YouAreNotFred)
End

Если вы тестировали это, вы бы в основном хотели протестировать ту часть, которая проверяет, является ли пользователь Фредом или нет. Вы действительно не хотите проверять Printerчасть вещей. Это было бы еще одним испытанием.

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


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

Ожидания позволяют вашей Mock выдавать ошибку, если она используется неправильно. Таким образом, в приведенном выше примере вы можете быть уверены, что Printer вызывается с HelloFred в тестовом примере "user is Fred". Если этого не произойдет, ваш издеватель может предупредить вас.

Поведение в издевательствах означает, что, например, ваш код сделал что-то вроде:

If Call Printer(HelloFred) Returned SaidHello Then
    Do Something
End

Теперь вы хотите проверить, что делает ваш код при вызове Printer и возвращает SaidHello, поэтому вы можете настроить Mock на возврат SaidHello при вызове с HelloFred.

Один хороший ресурс вокруг этого Мартин Fowlers разместить Mocks ли не Столбики

Дэвид Холл
источник
7

Ложные и тупые объекты являются важной частью модульного тестирования. На самом деле они проходят долгий путь, чтобы убедиться, что вы тестируете юниты , а не группы юнитов.

Короче говоря, вы используете заглушки, чтобы сломать зависимость SUT (тестируемая система) от других объектов и макетов, чтобы сделать это, и убедиться, что SUT вызвал определенные методы / свойства для зависимости. Это восходит к фундаментальным принципам модульного тестирования - то, что тесты должны быть легко читаемыми, быстрыми и не требующими настройки, что может подразумевать использование всех реальных классов.

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

Простой сценарий с использованием C # и Moq:

public interface IInput {
  object Read();
}
public interface IOutput {
  void Write(object data);
}

class SUT {
  IInput input;
  IOutput output;

  public SUT (IInput input, IOutput output) {
    this.input = input;
    this.output = output;
  }

  void ReadAndWrite() { 
    var data = input.Read();
    output.Write(data);
  }
}

[TestMethod]
public void ReadAndWriteShouldWriteSameObjectAsRead() {
  //we want to verify that SUT writes to the output interface
  //input is a stub, since we don't record any expectations
  Mock<IInput> input = new Mock<IInput>();
  //output is a mock, because we want to verify some behavior on it.
  Mock<IOutput> output = new Mock<IOutput>();

  var data = new object();
  input.Setup(i=>i.Read()).Returns(data);

  var sut = new SUT(input.Object, output.Object);
  //calling verify on a mock object makes the object a mock, with respect to method being verified.
  output.Verify(o=>o.Write(data));
}

В приведенном выше примере я использовал Moq для демонстрации заглушек и насмешек. Moq использует один и тот же класс для обоих - Mock<T>что делает его немного запутанным. Независимо от того, во время выполнения, тест не будет выполнен, если output.Writeне вызывается с данными как parameter, тогда как неудачный вызов input.Read()не провалит его.

Игорь Зевака
источник
4

Как еще один ответ, предложенный через ссылку на « Насмешки не заглушки », mocks - это форма «test double», которую можно использовать вместо реального объекта. Что отличает их от других форм тестовых двойников, таких как объекты-заглушки, заключается в том, что другие тестовые двойники предлагают проверку состояния (и, возможно, моделирование), тогда как макеты предлагают проверку поведения (и, возможно, моделирование).

С заглушкой вы можете вызывать несколько методов заглушки в любом порядке (или даже многократно) и определять успех, если заглушка получила значение или состояние, которое вы намеревались. Напротив, фиктивный объект ожидает вызова очень специфических функций в определенном порядке и даже определенное количество раз. Тест с фиктивным объектом будет считаться «неудачным» просто потому, что методы были вызваны в другой последовательности или количестве - даже если фиктивный объект имел правильное состояние после завершения теста!

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

Брент Ариас
источник
3

Частью использования фиктивных объектов является то, что они не должны быть реально реализованы в соответствии со спецификацией. Они могут просто дать фиктивные ответы. Например, если вам нужно реализовать компоненты A и B, и оба «вызывают» (взаимодействовать) друг с другом, то вы не можете проверить A, пока не будет реализован B, и наоборот. В тестовой разработке это проблема. Таким образом, вы создаете фиктивные («фиктивные») объекты для A и B, которые очень просты, но дают некоторый ответ, когда с ними взаимодействуют. Таким образом, вы можете реализовать и протестировать A, используя фиктивный объект для B.

LarsH
источник
1

Для php и phpunit это хорошо объяснено в документации phpunit. смотрите здесь документацию phpunit

В простом слове mocking object - это просто фиктивный объект вашего оригинала, который возвращает свое возвращаемое значение, это возвращаемое значение может быть использовано в тестовом классе

Гаутам Рай
источник
0

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

Мохсен Мср
источник