Насмешливый класс бетона - не рекомендуется

11

Я только что прочитал отрывок из книги «Растущее объектно-ориентированное программное обеспечение», в которой объясняются некоторые причины, по которым не рекомендуется издеваться над конкретным классом.

Вот пример кода юнит-теста для класса MusicCentre:

public class MusicCentreTest {
  @Test public void startsCdPlayerAtTimeRequested() {
    final MutableTime scheduledTime = new MutableTime();
    CdPlayer player = new CdPlayer() { 
      @Override 
      public void scheduleToStartAt(Time startTime) {
        scheduledTime.set(startTime);
      }
    }

    MusicCentre centre = new MusicCentre(player);
    centre.startMediaAt(LATER);

    assertEquals(LATER, scheduledTime.get());
  }
}

И его первое объяснение:

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

Я не могу понять, что именно он имеет в виду, когда говорит:

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

Я понимаю, что сервис соответствует MusicCentreроссийскому методу startMediaAt.

Что он подразумевает под «в другом месте»?

Полный отрывок находится здесь: http://www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html

Mik378
источник
Добавил комментарий в свой блог, так как я не смог понять, что он имел в виду из этих цитат.
oligofren
@oligofren Это действительно большая загадка :) ...
Mik378

Ответы:

6

Автор этого поста продвигает использование интерфейсов, а не использование классов-членов.

It turns out that my MusicCentre object only uses the starting and stopping methods on the CdPlayer, the rest are used by some other part of the system. I'm over-specifying my MediaCentre by requiring it to talk to a CdPlayer, what it actually needs is a ScheduledDevice.

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

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

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

  • Начало
  • стоп
  • Пауза
  • запись
  • случайный порядок воспроизведения
  • сэмплы, начало песни
  • сэмплы, случайный сэмпл песни
  • предоставлять информацию для СМИ
  • ...

Теперь его утверждение «Мне просто нужно начать и остановиться» имеет больше смысла. Использование конкретного объекта-члена вместо интерфейса делает будущие разработчики менее понятными относительно того, что действительно требуется. Запуск модульных тестов из MediaCentre для всех других функций в CdPlayer - пустая трата усилий по тестированию, поскольку они принадлежат состоянию «пофиг». Если Recordфункция не работает в этом случае, нам действительно все равно, так как она не требуется. Но будущий сопровождающий не обязательно будет знать это, основываясь на написанном коде.

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


источник
Спасибо за этот отличный ответ. Однако вы сказали: «Выполнение модульных тестов для всех других функций является пустой тратой усилий по тестированию, поскольку они относятся к состоянию« все равно »». Разве это не так: «Создание макетов для каждой из других функций является пустой тратой усилий по тестированию, поскольку они принадлежат состоянию« все равно ».»?
Mik378
@ Mik378 - да, это именно то, к чему я стремился, я просто сформулировал это по-другому. И я обновил свой ответ, чтобы сделать это более ясным.
Но я считаю, что термин «запуск модульных тестов» сбивает с толку. Это означало бы, что MusicCentre собирается провести юнит-тестирование своего соавтора ... тогда как на самом деле это MOCKS своего соавтора, чтобы провести юнит-тестирование своих СОБСТВЕННЫХ сервисов. Кстати, теперь я понимаю смысл :)
Mik378
@ Mik378 - мы говорим одно и то же, и, вероятно, для этого я использую не совсем точную терминологию. Извиняюсь за путаницу.
4

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

Подумав много об этом, я получаю возможную интерпретацию этой цитаты:

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

В приведенном выше примере планирование выражается всем полнофункциональным объектом с именем CDPlayer. Таким образом, это все еще приводит к неявной связи между MusicCentre«фактом планирования».

Так что, если мы начнем вводить конкретные классы и насмехаться над ними в объектах высокого уровня; когда мы хотим протестировать эти объекты, мы должны проанализировать каждый внедренный «конкретный» объект, чтобы увидеть, представляет ли он конкретные отношения, которые мы ДОЛЖНЫ СДЕЛАТЬ, потому что они скрыты (неявные). Напротив, кодирование ВСЕГДА через интерфейс позволяет разработчику напрямую выяснить, какие отношения будут обслуживаться высокоуровневым объектом, и, следовательно, обнаруживать функции, которые необходимо смоделировать, чтобы изолировать модульный тест.

Mik378
источник
Я думаю, что вы получили это сейчас. К сожалению, я не получил уведомление о вашем комментарии.
Стив Фриман
3

Сервис, который я имел в виду, был CDPlayer.scheduleToStartAt (). Это то, что MediaCentre называет - коллаборационист, который должен функционировать. MediaCentre - это тестируемый объект.

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

Это помогает?

Стив Фриман
источник
(автор этой замечательной статьи :)), что я хотел интерпретировать, это предложение: «Это затрудняет понимание того, может ли служба, поддерживающая эти отношения, быть релевантной в другом месте, и мне придется провести анализ снова в следующий раз, когда я буду работать с классом ". Что за анализ? Факт обнаружения того, какой метод объекта должен реализовывать отношения, так как этот явно скрыт?
Mik378