Даже если вам это не нравится, ваша работа иногда требует поддержки устаревших приложений. Это случилось со мной (к счастью, редко), и первое, что я ищу, прежде чем писать столько же, сколько один символ в унаследованной кодовой базе, — это модуль модульного тестирования. Большую часть времени я стараюсь сосредоточиться на покрытии кода той части приложения, которую мне нужно изменить, и стараюсь максимально улучшить ее, даже при полном отсутствии модульных тестов.
Настоящие неприятности случаются, когда дизайн не самый современный. Модульное тестирование требует имитации, что хорошо только тогда, когда внедрение зависимостей (а также методы инициализации) делает классы модульно-тестируемыми: это не относится к некоторым устаревшим приложениям, где есть путаница частных и статических методов или кода инициализации в конструкторы, не говоря уже о статических блоках.
Например, рассмотрим следующий пример:
public class ExampleUtils { public static void doSomethingUseful() { ... } } public class CallingCode { public void codeThatShouldBeTestedInIsolation() { ExampleUtils.doSomethingUseful(); ... } }
Невозможно правильно выполнить модульное тестирование метода codeThatShouldBeTestedInIsolation (), поскольку он зависит от другого немодного статического метода другого класса. Конечно, есть проверенные методы преодоления этого препятствия. Одним из таких методов было бы создание прокси-класса, который обернул бы вызов статического метода и внедрил бы этот класс в вызывающий класс следующим образом:
public class UsefulProxy { public void doSomethingUsefulByProxy() { ExampleUtils.doSomethingUseful(); } } public class CallingCodeImproved { private UsefulProxy proxy; public void codeThatShouldBeTestedInIsolation() { proxy.doSomethingUSeful(); ... } }
Теперь я могу ввести фиктивный UsefulProxy и, наконец, протестировать свой метод изолированно. Есть несколько недостатков, чтобы задуматься, хотя:
- Созданный код не предоставляет тесты, только способ сделать тесты возможными.
- При написании этого небольшого обходного пути вы не производили никаких тестов. На данный момент вы ничего не достигли.
- Вы изменили код перед тестированием и рискнули нарушить поведение! Конечно, пример не подразумевает никакой сложности, но это не всегда так в реальных приложениях.
- Вы сделали код более тестируемым, но только с дополнительным уровнем сложности.
По всем этим причинам я бы рекомендовал этот подход только в крайнем случае. Хуже того, есть проекты, которые полностью закрыты для простого рефакторинга, например, в следующем примере, который отображает статический инициализатор:
public class ClassWithStaticInitializer { static { ... } }
Как только класс ClassWithStaticInitializer загружается загрузчиком классов, статический блок будет выполнен, хорошо это или плохо (в свете модульного тестирования, он, вероятно, будет последним).
Мой модный фреймворк выбора — Mockito . Его разработчики позаботились о том, чтобы такие функции, как насмешка статическим методом, были недоступны, и я благодарен им за это. Это означает, что если я не могу использовать Mockito, это дизайнерский запах. К сожалению, как мы видели ранее, для решения устаревшего кода могут потребоваться такие функции. Вот тогда и вступает в PowerMock (и только потом — использование PowerMock в стандартном процессе разработки также является верным признаком того, что дизайн не совсем понятен).
С PowerMock вы можете оставить исходный код нетронутым и продолжать тестирование, чтобы начать с уверенностью изменять код. Вот тестовый код для первого устаревшего фрагмента, использующего Mockito и TestNG:
@PrepareForTest(ExampleUtils.class) public class CallingCodeTest { private CallingCode callingCode; @BeforeMethod protected void setUp() { mockStatic(ExampleUtils.class); callingCode = new CallingCode(); } @ObjectFactory public IObjectFactory getObjectFactory() { return new PowerMockObjectFactory(); } @Test public void callingMethodShouldntRaiseException() { callingCode.codeThatShouldBeTestedInIsolation(); assertEquals(getInternalState(callingCode, "i"), 1); } }
Делать особо нечего, а именно:
- Аннотируйте тестовые классы (или отдельные тестовые методы) с помощью @PrepareForTest, который ссылается на классы или целые пакеты. Это говорит PowerMock разрешить манипулирование байт-кодом этих классов, эффективная инструкция выполняется на следующем шаге.
- Смоделируйте нужные методы с помощью доступной палитры методов mockXXX ().
- Укажите фабрику объектов в методе, который возвращает IObjectFactory и аннотирован @ObjectFactory.
Также обратите внимание, что с помощью класса Whitebox мы можем получить доступ к внутреннему состоянию класса ( то есть закрытым переменным). Даже несмотря на то, что это плохо, альтернатива — риск с унаследованным кодом без использования тестов хуже: помните, что наша цель — уменьшить вероятность появления новых ошибок.
Список возможностей PowerMock доступен здесь для Mockito. Обратите внимание, что подавление статических блоков в TestNG сейчас невозможно .
Вы можете найти источники для этой статьи здесь в формате Maven.