Тестируете код, который зависит от Enterprise Library, даже если он не предоставляет интерфейсы?

Может быть, я демонстрирую свое непонимание внедрения зависимостей и тестирования, но я не понимаю, как использование внедрения зависимостей с классами, которые не реализуют интерфейсы, вообще помогает мне в тестировании?

Например, в документации Enterprise Library 5.0 говорится об использовании контейнера Unity для создания экземпляров. В нем говорится, что это помогает «тестируемости: тривиально изолировать классы от зависимостей при использовании стиля внедрения зависимостей». MSDN

Как мне использовать это в моих приборах модульного тестирования? В их примере есть конструктор с параметрами в виде классов, а не интерфейсов:

public class TaxCalculator 
{
  private ExceptionManager _exceptionManager;
  private LogWriter _logWriter;

  public TaxCalculator(ExceptionManager em, LogWriter lw) 
  {
    this._exceptionManager = em;
    this._logWriter = lw;
  }
}

person Bob Wintemberg    schedule 02.04.2012    source источник


Ответы (3)


Чтобы ответить на вопрос «Как мне протестировать код Enterprise Library», вы не будете. Тестирование чужих вещей — это работа других людей. Любые интерфейсы или абстракции в Enterprise Library или любой другой сторонней библиотеке существуют для их собственных целей абстракции, а не для ваших.

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

Чтобы протестировать свой собственный код, разработанный таким образом, для тестов на уровне модуля/компонента вы должны просто использовать Test Doubles для тех интерфейсов, которые вы определили сами (например, IMyOwnLogger). Чтобы протестировать адаптеры, которые вы пишете для адаптации к сторонним библиотекам, вы должны написать интеграционные тесты. Чтобы проверить, что все это работает вместе, вы должны написать приемочные тесты, которые управляют приложением через пользовательский интерфейс или подкожно.

Для получения дополнительной информации об этом представлении ознакомьтесь с моей статьей: "Лучшие практики TDD: не издевайтесь над другими ».

person Derek Greer    schedule 02.04.2012
comment
Я изменил название, чтобы отразить тот факт, что я хочу тестировать не Enterprise Library, а свой собственный код, зависящий от Enterprise Library. - person Bob Wintemberg; 02.04.2012
comment
Я обновил ответ, чтобы прямо рассказать о том, как вы будете тестировать свой собственный код в свете внешних зависимостей. - person Derek Greer; 02.04.2012
comment
Кстати, определение собственных интерфейсов, которые описывают потребности вашего приложения (например, ведение журнала), звучит хорошо. Отделение инфраструктуры от бизнес-кода — это здорово. Но терять крутые фичи фреймворка, добавлять еще один уровень абстракции, терять оптимизацию и производительность — это точно нехорошо. - person Sergey Berezovskiy; 03.04.2012
comment
Вы не теряете функции фреймворка, если они не нужны вашему приложению. Следуя хорошей методологии проектирования, вы должны начать с определения того, что нужно вашему приложению, а затем адаптировать любые внешние зависимости, выпадающие из этого проекта, к доступным платформам. Действуя таким образом, вы никогда не получите нужную вам функцию, но от которой вы каким-то образом отрезаны. Я не предлагаю вам писать интерфейсы с наименьшим общим знаменателем, а скорее писать интерфейсы, которые нужны вашему приложению. Что касается того, что шаблон адаптера каким-то образом вредит вашему приложению, я лично этого не видел. - person Derek Greer; 03.04.2012
comment
@DerekGreer, ваш блог со статьями теперь является спам-сайтом. Для других вы можете увидеть исходный контент благодаря проекту веб-архива: https://web.archive.org/web/20140923101818/http://freshbrewedcode.com/derekgreer/2012/04/01/tdd-best-practices-dont-mock-others/ - person Brice; 02.10.2018

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

public abstract class LogWriter
{
    public abstract void Write(string message);
}

Итак, создать макет абстрактного класса не проблема:

Mock<LogWriter> logWriter = new Mock<LogWriter>();
TaxCalculator calc = new TaxCalculator(logWriter.Object);

Если вы не выполняете модульное тестирование, я не вижу проблем с передачей неабстрактных параметров из-за принципа YAGNI. Если мне не нужна другая реализация ExceptionManager, то зачем мне создавать над ней абстракцию? Но если я буду делать TDD, то мне точно понадобятся как минимум две реализации класса. Один настоящий и один макет/заглушка.

Кстати, будьте осторожны с антишаблоном локатора сервисов.

ОБНОВЛЕНИЕ: Не понял, что вы имеете в виду существующие классы Microsoft.Practices.EnterpriseLibrary (что мне не нравится). Я думаю, что это еще один провал дизайна команды Microsoft.Practices. Создание «запечатанного» класса ExceptionManager, который не реализует никаких интерфейсов/базовых классов, убивает тестируемость.

person Sergey Berezovskiy    schedule 02.04.2012
comment
Абстракции в сторонних библиотеках существуют для их собственных целей, а не для того, чтобы предоставить вам абстракцию для использования. Кроме того, корпоративная библиотека, вероятно, является идеальным сценарием для использования ключевого слова seal. P&P всегда заявляла, что они не намерены использовать EL напрямую, а будут служить примером для написания кода. Они не хотят, чтобы люди расширяли свои возможности, а затем жаловались, когда они вносят изменения в дизайн в более поздних версиях. - person Derek Greer; 02.04.2012
comment
Итак, вы думаете, например. ILog в log4net не предоставляет мне абстракцию для использования? - person Sergey Berezovskiy; 03.04.2012
comment
Воспринимайте это утверждение как гиперболу, если хотите. Различные поставщики программного обеспечения включают интерфейсы по разным причинам. Иногда интерфейсы являются побочным продуктом методологии тестирования поставщика. В других случаях может быть несколько реализаций, на которые опирается компонент более высокого уровня. Некоторые поставщики могут предоставлять интерфейсы как шов в надежде, что будущие изменения дизайна будут меняться без изменения общедоступного интерфейса. И да, некоторые могут предоставлять интерфейсы, думая, что это будет одна абстракция, которая будет управлять ими всеми. Несмотря на это, вы не должны полагаться на них в своих собственных проектах. - person Derek Greer; 03.04.2012
comment
ExceptionManager не запечатан. На самом деле он (и другие классы *manager в Entlib) являются абстрактными классами, специально предназначенными для насмешек. То, что они не являются интерфейсами, не означает, что они не являются абстракциями. - person Chris Tavares; 05.04.2012

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

person Matt Mills    schedule 02.04.2012