Когда вы начнете свое путешествие по пути модульного тестирования, вы обнаружите, что частью того, что делает код тестируемым, является концепция внедрения зависимостей . По мере дальнейшего изучения вы увидите, что люди упоминают различные структуры внедрения зависимостей. Вы, естественно, можете предположить, что для внедрения Dependency Injection вам нужно будет выбрать использование инфраструктуры внедрения Dependency Injection.
Но Dependency Injection не имеет ничего общего с использованием структуры Dependency Injection. Рамки есть потому что:
1. Большая часть нашего существующего кода — это код, который имеет слишком много зависимостей, и инфраструктура помогает нам сломать эти зависимости без необходимости рефакторинга слишком большой части нашего кода и
2. дать нам возможность легко поменять один объект на другой, когда наш код структурирован таким образом, что вообще не имеет зависимостей.
Итак, что же такое инъекция в зависимости?
Однажды я услышал этот принцип, который объясняет это лучше всего,
Классы должны либо создавать вещи, либо делать вещи, но ни один класс не должен делать и то, и другое.
Большая часть нашего кода выглядит примерно так:
private static void ReceiveSAMLResponse( out SAMLResponse samlResponse, out String relayState) { // Receive the SAML response over the //specified binding. XmlElement samlResponseXml; ServiceProvider.ReceiveSAMLResponseByHTTPPost( HttpContext.Current.Request, out samlResponseXml, out relayState); SAMLResponse resp = new SAMLResponse(samlResponseXml); XmlElement samlAssertionElement = resp.GetSignedAssertions()[0]; // Verify the response's signature. XmlDocument doc = new XmlDocument(); //metadata file path (holds the description key). doc.Load(HttpContext.Current.Server.MapPath("~/SAML.xml")); XmlElement root = doc.DocumentElement; if (!SAMLAssertionSignature.Verify( samlAssertionElement, ReadMetadata(root))) { samlResponse = null; relayState = null; return; } // Deserialize the XML. samlResponse = new SAMLResponse(samlResponseXml); }
Да, это настоящий код из системы, над которой я работал. Оригинальный код был написан три или четыре года назад, и этот конкретный код является кодом, который мне дал другая компания. Я просто использовал копирование и вставку наследования, чтобы заставить его работать в нашем коде.
Это не значит, что я не написал треску, у которой столько же проблем.
В этом коде есть несколько ошибок, но сейчас я хочу сосредоточиться только на проблеме внедрения зависимостей.
Все, что пытается сделать этот код, — это десериализовать зашифрованный объект samlResponse, который был размещен в форме входа. По крайней мере, этот код отсутствует в форме входа! Это так много значит для этого.
НО ЗДЕСЬ НАХОДЯТСЯ МЕСТА, ГДЕ ЭТО ЗАВИСИМО ОТ СЛИШКОМ МНОГО:
- ServiceProvider является статическим классом и вызывается напрямую.
- ReceiveSAMLResponseByHTTPPost зависит от объекта Request, который мы получаем из HttpContext.
- Я создаю новый объект SAMLResponse прямо перед вызовом GetSignedAssertions ()
- Я создаю новый объект XmlDocument, чтобы я мог загрузить файл SAML.xml
- SAMLAssertionSignature является статическим и вызывается напрямую
На самом деле, этот метод представляет собой один большой беспорядок зависимости. И я очень хорошо понимаю, в каком беспорядке это происходит, потому что компания, которой мы написали этот код для совсем недавно переключенных поставщиков Поскольку мы пытались заставить это работать, у меня не было способа протестировать этот код изолированно без добавления кода непосредственно в этот метод. Мы работали, но это не должно быть так сложно.
ТАК ЧТО ЗДЕСЬ, ЧТО Я ДЕЛАЛ С ЭТИМ КОДОМ.
- Поскольку ServiceProvider и SAMLAssertionSignature являются вызовами стороннего API, я бы обернул их в не статический класс, который я могу создать.
- Я хотел бы иметь класс, который в конечном итоге вызывает этот метод, либо передавать зависимые объекты в конструктор, либо передавать их в вызывающий метод, либо устанавливать свойства в классе. Вот что значит внедрять зависимости.
- Я бы искал способ избежать создания нового объекта SAMLResponse. Я не смотрю на документацию по API, но было бы здорово, если бы я мог создать новый объект и вызвать метод или установить свойство, чтобы присвоить ему переменную samlResponseXml. Если бы мне пришлось, я бы, вероятно, поместил SAMLResponse в другой класс, чтобы получить эту функциональность.
- Наконец, я бы сделал каждый зависимый объект на основе интерфейса, чтобы я мог поменять их местами. В случае некоторых из этих классов мне может понадобиться обернуть их в другой класс, который я контролирую, чтобы я мог реализовать интерфейс. Например, при существующем положении вещей я не смог бы создать поддельный объект запроса, поскольку запрос — это системный класс, который не реализует интерфейс.
ПРЕИМУЩЕСТВА
После внесения всех этих изменений я смогу создать тестовый комплект для этого кода, создать поддельные версии объектов и убедиться, что этот метод выполняет то, что мы для него делаем.
Это было бы особенно полезно на прошлой неделе, когда мы пытались заставить все это работать с новым провайдером. В этом случае я мог бы подделать объект запроса с тем, что нам посылали, и запустить его через отладчик, чтобы выяснить, что было не совсем правильно.
БЕЗ ЗАВИСИМОСТИ ОТ ЗАВИСИМОСТИ
Наконец, вы заметите, что нигде в этом коде мне не пришлось использовать платформу Dependency Injection, чтобы все это работало.