Я только что прочитал отрывок из книги «Растущее объектно-ориентированное программное обеспечение», в которой объясняются некоторые причины, по которым не рекомендуется издеваться над конкретным классом.
Вот пример кода юнит-теста для класса 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
Ответы:
Автор этого поста продвигает использование интерфейсов, а не использование классов-членов.
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
функция не работает в этом случае, нам действительно все равно, так как она не требуется. Но будущий сопровождающий не обязательно будет знать это, основываясь на написанном коде.В конечном счете, авторская предпосылка заключается в том, чтобы использовать только то, что необходимо, и прояснить будущим сопровождающим то, что требовалось ранее. Цель состоит в том, чтобы минимизировать переработку / повторный анализ модуля кода во время последующего обслуживания.
источник
Подумав много об этом, я получаю возможную интерпретацию этой цитаты:
Указанная «услуга» соответствует «факту планирования». Это может быть выражено с помощью хорошо названного и «сфокусированного на одной роли» интерфейса с именем «ScheduledDevice» или неявно выражено конкретной реализацией метода, не зависящей от каких-либо интерфейсов.
В приведенном выше примере планирование выражается всем полнофункциональным объектом с именем
CDPlayer
. Таким образом, это все еще приводит к неявной связи междуMusicCentre
«фактом планирования».Так что, если мы начнем вводить конкретные классы и насмехаться над ними в объектах высокого уровня; когда мы хотим протестировать эти объекты, мы должны проанализировать каждый внедренный «конкретный» объект, чтобы увидеть, представляет ли он конкретные отношения, которые мы ДОЛЖНЫ СДЕЛАТЬ, потому что они скрыты (неявные). Напротив, кодирование ВСЕГДА через интерфейс позволяет разработчику напрямую выяснить, какие отношения будут обслуживаться высокоуровневым объектом, и, следовательно, обнаруживать функции, которые необходимо смоделировать, чтобы изолировать модульный тест.
источник
Сервис, который я имел в виду, был CDPlayer.scheduleToStartAt (). Это то, что MediaCentre называет - коллаборационист, который должен функционировать. MediaCentre - это тестируемый объект.
Идея состоит в том, что если я укажу только то, от чего зависит MediaCentre, а не класс реализации, я могу дать этой роли зависимости имя и поговорить об этом. Все, что MediaCentre должен знать, это то, что он общается с ScheduledDevices. Поскольку остальная часть системы изменяется, мне не нужно будет менять MediaCentre, если не меняются его функции.
Это помогает?
источник