Использование Mockito для тестирования абстрактных классов

StackOverflow https://stackoverflow.com/questions/1087339

Вопрос

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

Могу ли я сделать это, используя mocking framework (я использую Mockito) вместо ручной обработки моего макета?Каким образом?

Это было полезно?

Решение

Следующее предложение позволяет вам тестировать абстрактные классы без создания "реального" подкласса - макета является подкласс.

использование Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS), затем имитируйте любые вызываемые абстрактные методы.

Пример:

public abstract class My {
  public Result methodUnderTest() { ... }
  protected abstract void methodIDontCareAbout();
}

public class MyTest {
    @Test
    public void shouldFailOnNullIdentifiers() {
        My my = Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS);
        Assert.assertSomething(my.methodUnderTest());
    }
}

Примечание:Прелесть этого решения в том, что вы не иметь для реализации абстрактных методов, при условии, что они никогда не вызываются.

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

Другие советы

Если вам просто нужно протестировать некоторые конкретные методы, не касаясь ни одного из тезисов, вы можете использовать CALLS_REAL_METHODS (см. Ответ Мортена), но если конкретный тестируемый метод вызывает некоторые из абстрактных или нереализованных методов интерфейса, это не сработает - Mockito будет жаловаться "Не удается вызвать реальный метод в интерфейсе Java".

(Да, это паршивый дизайн, но некоторые фреймворки, напримерГобелен 4, как бы навязываю это тебе.)

Обходной путь заключается в том, чтобы изменить этот подход - использовать обычное фиктивное поведение (т. Е. Все высмеивается / заглушается) и использовать doCallRealMethod() для явного вызова конкретного тестируемого метода.Например.

public abstract class MyClass {
    @SomeDependencyInjectionOrSomething
    public abstract MyDependency getDependency();

    public void myMethod() {
        MyDependency dep = getDependency();
        dep.doSomething();
    }
}

public class MyClassTest {
    @Test
    public void myMethodDoesSomethingWithDependency() {
        MyDependency theDependency = mock(MyDependency.class);

        MyClass myInstance = mock(MyClass.class);

        // can't do this with CALLS_REAL_METHODS
        when(myInstance.getDependency()).thenReturn(theDependency);

        doCallRealMethod().when(myInstance).myMethod();
        myInstance.myMethod();

        verify(theDependency, times(1)).doSomething();
    }
}

Обновлено для добавления:

Для методов, не содержащих void, вам нужно будет использовать thenCallRealMethod() вместо этого, например:

when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod();

В противном случае Mockito будет жаловаться "Обнаружено незаконченное заглушение".

Вы можете добиться этого с помощью шпиона (хотя используйте последнюю версию Mockito 1.8+).

public abstract class MyAbstract {
  public String concrete() {
    return abstractMethod();
  }
  public abstract String abstractMethod();
}

public class MyAbstractImpl extends MyAbstract {
  public String abstractMethod() {
    return null;
  }
}

// your test code below

MyAbstractImpl abstractImpl = spy(new MyAbstractImpl());
doReturn("Blah").when(abstractImpl).abstractMethod();
assertTrue("Blah".equals(abstractImpl.concrete()));

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

При тестировании абстрактного класса вы хотите выполнить неабстрактные методы тестируемого объекта (SUT), поэтому имитирующий фреймворк - это не то, что вам нужно.

Часть путаницы заключается в том, что в ответе на вопрос, на который вы ссылались, говорилось о создании вручную макета, который расширяется из вашего абстрактного класса.Я бы не назвал такой класс насмешкой.Макет - это класс, который используется в качестве замены зависимости, запрограммирован с ожиданиями и может быть запрошен, чтобы проверить, оправдались ли эти ожидания.

Вместо этого я предлагаю определить неабстрактный подкласс вашего абстрактного класса в вашем тесте.Если это приводит к слишком большому объему кода, то это может быть признаком того, что ваш класс трудно расширить.

Альтернативным решением было бы сделать сам ваш тестовый пример абстрактным с помощью абстрактного метода для создания SUT (другими словами, тестовый пример использовал бы Шаблонный Метод шаблон проектирования).

Попробуйте использовать пользовательский ответ.

Например:

import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class CustomAnswer implements Answer<Object> {

    public Object answer(InvocationOnMock invocation) throws Throwable {

        Answer<Object> answer = null;

        if (isAbstract(invocation.getMethod().getModifiers())) {

            answer = Mockito.RETURNS_DEFAULTS;

        } else {

            answer = Mockito.CALLS_REAL_METHODS;
        }

        return answer.answer(invocation);
    }
}

Он вернет макет для абстрактных методов и вызовет реальный метод для конкретных методов.

Что действительно заставляет меня чувствовать себя плохо из-за издевательства над абстрактными классами, так это тот факт, что ни конструктор по умолчанию YourAbstractClass() не вызывается (отсутствует super() в mock), и, похоже, в Mockito нет никакого способа инициализировать фиктивные свойства по умолчанию (например, свойства списка с пустым ArrayList или LinkedList).

Мой абстрактный класс (в основном генерируется исходный код класса) НЕ предоставляет ни внедрения средства установки зависимостей для элементов списка, ни конструктора, в котором он инициализирует элементы списка (которые я пытался добавить вручную).

Только атрибуты класса используют инициализацию по умолчанию:закрытый список dep1 = новый список массивов;закрытый список dep2 = новый список массивов

Таким образом, нет способа издеваться над абстрактным классом без использования реализации реального объекта (например, определение внутреннего класса в классе модульного тестирования, переопределение абстрактных методов) и слежки за реальным объектом (который выполняет правильную инициализацию поля).

Очень жаль, что только PowerMock мог бы помочь здесь в дальнейшем.

Предполагая, что ваши тестовые классы находятся в том же пакете (под другим исходным кодом root), что и ваши тестируемые классы, вы можете просто создать макет:

YourClass yourObject = mock(YourClass.class);

и вызывайте методы, которые вы хотите протестировать, точно так же, как и любой другой метод.

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

Все, что это делает, - это создает конкретный экземпляр YouClass и избавляет вас от необходимости предоставлять пустые реализации каждого абстрактного метода.

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

Вы можете расширить абстрактный класс анонимным классом в вашем тесте.Например (используя Junit 4):

private AbstractClassName classToTest;

@Before
public void preTestSetup()
{
    classToTest = new AbstractClassName() { };
}

// Test the AbstractClassName methods.

Вы можете создать экземпляр анонимного класса, внедрить свои mocks и затем протестировать этот класс.

@RunWith(MockitoJUnitRunner.class)
public class ClassUnderTest_Test {

    private ClassUnderTest classUnderTest;

    @Mock
    MyDependencyService myDependencyService;

    @Before
    public void setUp() throws Exception {
        this.classUnderTest = getInstance();
    }

    private ClassUnderTest getInstance() {
        return new ClassUnderTest() {

            private ClassUnderTest init(
                    MyDependencyService myDependencyService
            ) {
                this.myDependencyService = myDependencyService;
                return this;
            }

            @Override
            protected void myMethodToTest() {
                return super.myMethodToTest();
            }
        }.init(myDependencyService);
    }
}

Имейте в виду, что видимость должна быть protected для собственности myDependencyService из абстрактного класса ClassUnderTest.

Whitebox.invokeMethod(..) может быть удобен в этом случае.

Mockito позволяет издеваться над абстрактными классами с помощью @Mock аннотация:

public abstract class My {

    public abstract boolean myAbstractMethod();

    public void myNonAbstractMethod() {
        // ...
    }
}

@RunWith(MockitoJUnitRunner.class)
public class MyTest {

    @Mock(answer = Answers.CALLS_REAL_METHODS)
    private My my;

    @Test
    private void shouldPass() {
        BDDMockito.given(my.myAbstractMethod()).willReturn(true);
        my.myNonAbstractMethod();
        // ...
    }
}

Недостатком является то, что он не может быть использован, если вам нужны параметры конструктора.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top