Как проверить окончательные и статические методы проекта утилиты?

Я пытаюсь выполнить модульное тестирование для aproject, он использует устаревший проект "утилита", который усеян статическими методами, и многие из классов являются окончательными или их методы являются окончательными. Я вообще не могу обновить старый проект.

JMock и EasyMock оба задувают окончательные методы, и я не вижу приятного способа протестировать статические вызовы. Какие существуют методы тестирования?

Ответы

Ответ 1

Если вы можете реорганизовать свой код, вы можете привязать свои вызовы к окончательным/статическим методам в простых методах экземпляра, например:

protected Foo doBar(String name) {
    return Utility.doBar(name);
}

Это позволяет вам переопределить метод оболочки в unit test, чтобы вернуть экземпляр foo.

В качестве альтернативы вы можете использовать Powermock, который расширяет Easymock (и Mockito), чтобы позволить насмехаться над финальными и статическими методами:

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

Здесь пример тестирует насмешливый статический окончательный метод, пример показывает, как издеваться над другими типами:

@Test
public void testMockStaticFinal() throws Exception {
    mockStatic(StaticService.class);
    String expected = "Hello altered World";
    expect(StaticService.sayFinal("hello")).andReturn("Hello altered World");
    replay(StaticService.class);

    String actual = StaticService.sayFinal("hello");

    verify(StaticService.class);
    assertEquals("Expected and actual did not match", expected, actual);

    // Singleton still be mocked by now.
    try {
        StaticService.sayFinal("world");
            fail("Should throw AssertionError!");
    } catch (AssertionError e) {
        assertEquals("\n  Unexpected method call sayFinal(\"world\"):", 
            e.getMessage());
    }
}

Ответ 2

Как насчет уровня косвенности/инъекции зависимостей?

Поскольку проект устаревшей утилиты - это ваша зависимость, создайте интерфейс, чтобы отделить его от вашего кода. Теперь ваша реальная/производственная реализация этого интерфейса делегирует устаревшие утилиты.

public LegacyActions : ILegacyActions
{
  public void SomeMethod() { // delegates to final/static legacy utility method }
}

Для ваших тестов вы можете создать макет этого интерфейса и избежать взаимодействия с устаревшей утилитой.

Ответ 3

JMockit позволяет вам высмеивать статические методы и финальные классы. Я предполагаю, что он использует некоторый classloadin-fu, хотя я действительно не изучал это.

JMockit Expectations API позволяет устанавливать ожидания для любого вида вызова метода (для интерфейсов, абстрактных классов, конкретных конечных или не конечных классов и для статических методов), а также для создания экземпляров классов с помощью любых конструкторов.

Ответ 4

Как уже указывалось, JMockit можно использовать. Пример:

@Test
public void mockStaticAndFinalMethods(@Mocked LegacyService mock) {
    new Expectations() {{
        LegacyService.staticMethod("hello"); result = "Hello altered World";
    }};

    String actual = LegacyService.staticMethod("hello");
    new LegacyService().finalMethod(123, "test");

    assertEquals("Hello altered World", actual);

    new Verifications() {{
        mock.finalMethod(123, "test"); // verify this call occurred at least once
    }};
}

Ответ 5

Если ваш метод без рефакторинга использует что-то вроде JNDI для подключения к другой службе, я бы подумал о запуске службы JDNI и заполнении ее с помощью заглушек, которыми вы управляете. Это боль, но относительно простая. Это может означать настройку базы данных или прослушивателя JMS или что-то еще, но должна быть легкая реализация Java, которую вы можете опробовать в тестах.

Ответ 6

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