2009-07-06 4 views
162

Я бы хотел протестировать абстрактный класс. Конечно, я могу manually write a mock, который наследуется от класса.Использование Mockito для тестирования абстрактных классов

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

+2

Начиная с Mockito [1.10.12] (http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html#30), Mockito поддерживает шпионские/издевательские абстрактные классы напрямую: 'SomeAbstract spy = spy (SomeAbstract.class); ' – pesche

ответ

267

Следующее предложение давайте проверим абстрактные классы, не создавая «настоящий» подкласс - Mock - это подкласс.

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

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

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

+0

+1 за помощь в решении моей проблемы, которая не была связана с вопросом. CALLS_REAL_METHODS был полезен для инъекции объекта-заглушки в мой SUT. Заготовка имела больше смысла, чем макет, поскольку возвращаемые значения были немного сложными. – Snekse

+10

Как отмечено ниже, это не работает, когда абстрактный класс вызывает абстрактные методы для тестирования, что часто бывает так. –

+6

Это фактически работает, когда абстрактный класс вызывает абстрактные методы. Просто используйте синтаксис doReturn или doNothing вместо Mockito.when для обнуления абстрактных методов, и если вы заглушите какие-либо конкретные вызовы, убедитесь, что на первом этапе происходит прерывание абстрактных вызовов. –

2

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

YourClass yourObject = mock(YourClass.class); 

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

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

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

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

+3

Но использование макета не будет проверять конкретные методы YourClass, или я ошибаюсь? Это не то, что я ищу. – ripper234

+1

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

+0

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

13

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

При тестировании абстрактного класса вы хотите выполнить не абстрактные методы объекта Subject Test (SUT), так что насмешливая структура не то, что вы хотите.

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

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

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

6

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

Например:

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

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

15

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

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

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

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

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

Слишком плохо, что только PowerMock поможет здесь.

66

Если вам просто нужно проверить некоторые из конкретных методов, не касаясь любой из тезисов, вы можете использовать CALLS_REAL_METHODS (см Morten's answer), но если конкретный метод испытуемый называет некоторые из тезисов, или нереализованных методов интерфейса, это не будет работать - 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 будет жаловаться «Неоконченная обнаружено обрушение."

+6

Это будет работать в некоторых случаях, однако Mockito не вызывает конструктор базового абстрактного класса с помощью этого метода. Это может привести к сбою «реального метода» из-за созданного непредвиденного сценария. Таким образом, этот метод не будет работать и во всех случаях. –

+2

Да, вы не можете рассчитывать на состояние объекта вообще, только код в вызываемом методе. –

1

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

private AbstractClassName classToTest; 

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

// Test the AbstractClassName methods. 
0

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

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