Статьи

Обход Гинормозных объектов контекста

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

О «Обходе вокруг больших объектов контекста» ( http://misko.hevery.com/2008/07/24/how-to-write-3v1l-untestable-code/) — эта ситуация возникает у меня, когда [я] пытаюсь получить настройки конфигурации в моем приложении. Я хочу, чтобы объекты глубже в системе имели некоторые настраиваемые значения, поступающие извне системы, и я не хочу, чтобы все по пути узнали о том, что это за настраиваемые значения. Как вы справляетесь с этим делом? Я имею в виду набор консольных утилит, которые принимают такие параметры, как количество повторных попыток для различных действий, допустимые тайм-ауты, уровни ведения журнала и другие вещи. Некоторые настройки будут интересны многим компонентам, а другие — только одному, и, похоже, если я их разложу, то, кто бы ни занимался конструкцией, должен будет много знать о том, что классы настроек знают и о которых не знают.

// I would hate the class constructing the
// FileCopier to know it is interested in
// all of these!
FileCopier copier = new FileCopier(logger,
           copierSettings, generalTimeouts);

// I end up doing this:
FileCopier copier = new FileCopier(configuration);
// and inside..
FileCopier(Configuration configuration) {
  X = configuration.GetSetting(“X”, DefaultValue);
  …
}

Давайте рассмотрим каждый из них по очереди:

  1. «Я бы не хотел, чтобы класс, создающий FileCopier, знал, что он заинтересован во всем этом!» Почему это проблема? как обсуждалось в « Нарушение закона Деметры, все равно что искать иголку в стоге сена»«Когда вы запрашиваете что-то явно, это облегчает прохождение test-double для этих зависимостей и, следовательно, делает код тестируемым. Он не только тестируемый, но и понятен. Если я новичок в базе кода, я смотрю на конструктор и вижу, что logger, copierSettings и timeouts — вещи, которые меня могут заинтересовать. С другой стороны, передавая объект Context, мне трудно узнать, какие части из объекта Context интересует тестируемый класс. Это также усложняет тестирование. Теперь мне нужно создать Mock Context и переопределить все getLogger (), getCoppierSettings () и так далее, чтобы эти методы возвращали реальные тестовые двойники, представляющие интерес. Но как я узнаю, что нужно издеваться? Мне нужно прочитать код, я не могу просто посмотреть на конструктор и узнать. Но есть еще:Предположим, я хочу повторно использовать класс FileCopier в другом проекте. В этом случае мне нужно повторно использовать все классы, зависящие от времени компиляции. В данном случае это контекст. Но контекст — это кухонная раковина, и он знает обо всем в вашем приложении, что означает, что я должен также повторно использовать эти объекты. Таким образом, код с объектами Context не может быть повторно использован / перекомпилирован.
  2. Место, где наша ментальная модель расходится, находится в следующем предложении: «Я не хочу, чтобы все по пути узнали о том, каковы эти настраиваемые значения». Страх состоит в том, что для вызова конструктора вызывающая сторона должна знать о параметрах, а вызывающая сторона должна знать и так далее. Мы говорили об этом в « Мифе о внедрении зависимости: прохождение ссылок ». Однако мы совершаем еще одну ошибку. Мы смешиваем создание объектов (и ищем) с логикой приложения, см. « Как думать о« новом »операторе в отношении модульного тестирования ». Основная ошибка, которую люди допускают в этой точке, состоит в том, что они предполагают, что график построения объекта такой же, как график создания объекта., Это предположение в основном верно только в том случае, если вы добавляете новые операторы в логику своего приложения. Если новый оператор удален из логики приложения, тогда ваше приложение имеет две фазы. Фаза (1) соединяет объекты вместе, чтобы создать экземпляр приложения. В Фазе (1) все, что мы делаем, это вызываем новые операторы и передаем правильные зависимости. Мы не вызываем никаких методов! На этапе (2) мы выполняем приложение, здесь мы держимся подальше от операторов new и вызываем методы, чтобы объекты могли взаимодействовать. После создания приложения нам больше не нужно передавать информацию о конфигурации. Мы просто выполняем методы на FileCopier.
  3. Ведение журнала является синглтоном, и мы обсуждали синглтоны здесь , здесь и здесь, Основная проблема заключается в том, что Singleton (шаблон проектирования) — это глобальное состояние, а глобальное состояние — отстой, из-за тестируемости, ремонтопригодности и нескольких других возможностей. Поэтому мы должны передать Logger через конструктор. Ну, это зависит … Если вы хотите утверждать, что при определенных условиях создается конкретное сообщение журнала, у вас нет выбора, кроме как передать его. С другой стороны, если вы просто хотите войти в систему и не хотите проверять это работает правильно, чем вы можете сделать Logger.getLogger (Class.class). Это не делает синглетонов в порядке, но это добрый вид зла. Причина в том, что с Logger информация течет только в одном направлении: в Logger.Ваше приложение не читает данные из регистратора и, что более важно, не ведет себя по-разному в зависимости от того, включен ли регистратор или выключен (он ведет себя по-разному только в том смысле, что ничего не печатается, но не в чувствую, что ваше приложение теперь вычисляет другой ответ.) Большинство синглетонов не попадают в эту редкую категорию, куда мы помещаем данные только в, и, следовательно, большинство синглетонов — это кошмар для тестирования.

ПРИМЕЧАНИЕ: когда я говорю «Синглтон», я имею в виду анти-шаблон проекта с глобальным полем экземпляра, как в частном статическом экземпляре Singleton = новый Singleton (); Это то, что делает Синглтон глобальным. Для сравнения с синглтоном (строчными буквами ‘s), как в одном экземпляре чего-либо без глобального поля экземпляра .

С http://misko.hevery.com