Люди умеют превращать конкретные примеры в обобщения. С другой стороны, это не так хорошо работает. Поэтому, когда я пишу об общих концепциях, людям трудно понять, как перевести общую концепцию в конкретный код. Чтобы исправить это, я постараюсь показать несколько примеров того, как создать веб-приложение с нуля. Но я не могу вместить все это в один пост в блоге … Итак, начнем с самого начала …
Вот как должен выглядеть ваш основной метод (независимо от сложности вашего приложения), если вы используете GUICE: ( src )
public static void main(String[] args) throws Exception { // Creation Phase Injector injector = Guice.createInjector( new CalculatorServerModule(args)); Server server = injector.getInstance(Server.class); // Run Phase server.start(); }
Или, если вы хотите сделать ручное внедрение зависимости: ( src )
public static void main(String[] args) throws Exception { // Creation Phase Server server = new ServerFactory(args) .createServer(); // Run Phase server.start(); }
Правда в том, что я не знаю, как проверить основной метод. Основной метод является статическим, и в результате нет мест, где мы можем ввести test-double. (Я знаю, что мы можем бороться со статикой с помощью статики, но мы уже говорили, что глобальное состояние плохое здесь , здесь и здесь ). Причина, по которой мы не можем это проверить, заключается в том, что в тот момент, когда вы запускаете метод main, запускается все приложение, и это не то, чего мы хотим, и мы ничего не можем сделать, чтобы предотвратить это.
Но метод настолько короткий, что я не потрудился его протестировать, поскольку у него есть действительно классные свойства:
- Обратите внимание, что фаза создания содержит код, который строит граф объектов приложения. Последняя строка запускает приложение. Разделение очень важно. Мы можем протестировать ServerFactory изолированно. Передавая ему разные аргументы и утверждая, что построен правильный граф объектов. Но для этого класс Factory не должен делать ничего, кроме построения графов объектов. Конструкторам объектов лучше ничего не делать, кроме назначения полей. Нет чтения файлов, запуска потоков или любой другой работы, которая может вызвать проблемы в модульном тесте. Все, что мы делаем, это просто создаем некоторый граф объектов. Построение графа контролируется аргументами командной строки, которые мы передали в конструктор. Таким образом, мы можем протестировать фазу создания изолированно с модульным тестом. (То же самое относится к примеру GUICE)
- Последняя строка запускает приложение. Здесь вы можете сделать все свои забавные темы, файл ввода-вывода и т. Д. Код. Тем не менее, поскольку приложение построено из множества взаимодействующих друг с другом объектов, каждый объект легко тестировать изолированно. В тесте я просто создаю экземпляр Сервера и передаю несколько тестовых пар в конструкторе, чтобы смоделировать не очень интересный / сложный для тестирования код.
Как видите, у нас есть четкое разделение ответственности за построение графов объектов от логики кода приложения. Если бы вы изучили код более подробно, то обнаружили бы, что все новые операторы перешли с фазы выполнения на фазу создания (см. Как думать о «новом» операторе).) И это очень важно. Новый оператор в коде приложения — враг тестирования, а новый в тестах и фабриках — ваш друг. (Причина в том, что в тестах мы хотим использовать test-double, которые обычно являются подклассом или реализацией родительского класса. Если код приложения вызывает новый, то вы никогда не сможете заменить это новое на подкласс или другую реализацию.) Ключ ответственность за создание объекта и код приложения — это две разные обязанности, и их не следует смешивать. Особенно в основном методе!
Хороший способ подумать об этом заключается в том, что вы хотите спроектировать свое приложение таким образом, чтобы вы могли управлять поведением приложения, управляя связыванием объектов друг с другом (граф соавторов объектов). Независимо от того, подключены ли вы к InMemory, репозиторию файлов или баз данных, PopServer или IMAPServer, LDAP или аутентификация на основе файлов. Все эти различные варианты поведения должны проявляться в виде разных графов объектов. Знание того, как соединять объекты вместе, должно храниться в вашем классе фабрики. Если вы хотите предотвратить запуск чего-либо в тесте, вы не должны ставить перед ним оператор if. Вместо этого вы подключаете другой граф объектов. Вы подключаете NullAthenticator вместо LDAPAuthenticator. Различная привязка ваших объектов — вот как тесты определяют, что будет запущено, а что отключено.Вот почему для тестов важно иметь контроль над новыми операторами (или, иначе говоря, в коде приложения нет новых операторов). Вот почему мы не знаем, как проверить основной метод. Основной метод является статическим и, следовательно, процедурным. Я не знаю, как проверить процедурный код, так как нет ничего, что можно подключить по-другому. Я не могу связать граф вызовов по-разному в процедурном мире, чтобы предотвратить выполнение вещей, граф вызовов определяется во время компиляции.Я не могу связать граф вызовов по-разному в процедурном мире, чтобы предотвратить выполнение вещей, граф вызовов определяется во время компиляции.Я не могу связать граф вызовов по-разному в процедурном мире, чтобы предотвратить выполнение вещей, граф вызовов определяется во время компиляции.
По моему опыту, этот метод обычно является одним из самых страшных кодов, которые я видел. Полный инициализации синглтона и потоков. Полностью непроверенный. Вы хотите, чтобы каждый объект просто объявлял свои зависимости в своем конструкторе. (Вот список вещей, о которых мне нужно знать) Затем, когда вы начнете писать Фабрику, она будет практически писать сама. Вы просто пытаетесь создать новый объект, который вам нужно вернуть, который объявляет его зависимости, вы, в свою очередь, пытаетесь обновить эти зависимости и т. Д. Если есть некоторые синглтоны, вам просто нужно убедиться, что вы вызываете оператор new только один раз. Но больше о фабриках в нашем следующем сообщении в блоге…