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

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

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

11 ответов


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

использовать 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();
    }
}

добавлено:

для непустых методов вам нужно будет использовать 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()));

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

при тестировании абстрактного класса вы хотите выполнить неабстрактные методы тестируемого объекта (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() в макете), ни, кажется, нет никакого способа в Mockito инициализировать свойства макета по умолчанию (e.G свойства списка с пустым ArrayList или LinkedList).

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

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

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

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


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

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.

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

@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();
        // ...
    }
}

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