Какова цель фиктивных объектов?

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

10 ответов

Решение

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

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

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

cook <- waiter <- customer

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

cook <- test driver

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

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

cook <- waiter <- test driver

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

или же

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

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

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

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

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

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

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

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

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

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

Например:

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 а также mailer фиктивные объекты программируются с ожидаемыми результатами.

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

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

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

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

Скажем, например, мы тестируем сервисный метод для обновления поля в виджете. И что в вашей архитектуре есть 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());

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

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

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

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

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

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

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


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

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

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

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

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

Один хороший ресурс об этом - пост Мартина Фаулерса.

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

Короче говоря, вы используете заглушки, чтобы разорвать зависимость 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() не подведет.

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

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

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

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

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

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

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

Другие вопросы по тегам