it-swarm.com.ru

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

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

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

182
ripper234

Следующее предложение позволяет вам тестировать абстрактные классы без создания «реального» подкласса - 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());
    }
}

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

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

286
Morten Lauritsen Khodabocus

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

(Да, это паршивый дизайн, но некоторые фреймворки, например, Tapestry 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 будет жаловаться на «Незавершенное обнаружение заглушки».

66
David Moles

Вы можете добиться этого с помощью шпиона (хотя используйте последнюю версию 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()));
16
Richard Nichols

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

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

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

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

Альтернативное решение состоит в том, чтобы сделать ваш тестовый пример сам по себе абстрактным, с абстрактным методом для создания SUT (другими словами, тестовый пример будет использовать Template Method design pattern).

13
NamshubWriter

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

Например:

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);
    }
}

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

8
Leopoldo Muller

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

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

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

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

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

5
Thomas Heiss

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

YourClass yourObject = mock(YourClass.class);

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

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

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

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

2
Nick Holt

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

private AbstractClassName classToTest;

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

// Test the AbstractClassName methods.
1
DwB

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

0
Smart Coder

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

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

0
Jorge Pastor

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

@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.

0
Samuel