Я бы хотел протестировать абстрактный класс. Конечно, я могу вручную написать макет, который наследуется от класса.
Могу ли я сделать это, используя насмешливую фреймворк (я использую Mockito) вместо того, чтобы рисовать мой макет? Как?
Я бы хотел протестировать абстрактный класс. Конечно, я могу вручную написать макет, который наследуется от класса.
Могу ли я сделать это, используя насмешливую фреймворк (я использую Mockito) вместо того, чтобы рисовать мой макет? Как?
Следующее предложение позволяет тестировать абстрактные классы без создания "реального" подкласса - 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());
    }
}
Примечание. Красота этого решения заключается в том, что вам не нужно реализовывать абстрактные методы, если они никогда не вызываются.
По моему честному мнению, это более аккуратно, чем использование шпиона, поскольку шпион требует экземпляра, а это значит, что вам нужно создать инстантабельный подкласс вашего абстрактного класса.
Если вам просто нужно проверить некоторые конкретные методы, не касаясь каких-либо рефератов, вы можете использовать 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 будет жаловаться "Обнаружено незавершенное обнаружение".
Вы можете добиться этого, используя шпион (однако используйте последнюю версию 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 frameworks предназначены для облегчения издевательства зависимостей тестируемого класса. Когда вы используете фальшивую структуру для издевательства над классом, большинство фреймворков динамически создают подкласс и заменяют реализацию метода кодом для обнаружения, когда метод вызывается и возвращает поддельное значение.
При тестировании абстрактного класса вы хотите выполнить не абстрактные методы Subject Under Test (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).
Мой абстрактный класс (в основном генерируется исходный код класса) НЕ предоставляет инъекцию установщика зависимостей для элементов списка, а также конструктор, в котором он инициализирует элементы списка (которые я пытался добавить вручную).
Только атрибуты класса используют инициализацию по умолчанию: private Список dep1 = новый ArrayList; private Список dep2 = новый ArrayList
Таким образом, нет никакого способа издеваться над абстрактным классом без использования реальной реализации объекта (например, определение внутреннего класса в классе unit test, переопределение абстрактных методов) и шпионаж реального объекта (который делает правильную инициализацию поля).
Слишком плохо, что только PowerMock поможет здесь далее.
Предполагая, что ваши тестовые классы находятся в одном пакете (под другим исходным корнем) в качестве тестируемых классов, вы можете просто создать mock:
YourClass yourObject = mock(YourClass.class);
и вызовите методы, которые вы хотите протестировать, как и любой другой метод.
Вам нужно предоставить ожидания для каждого метода, который вызывается с ожиданием по любым конкретным методам, вызывающим супер метод, - не уверен, как вы это сделаете с 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();
        // ...
    }
}
 Недостатком является то, что он не может использоваться, если вам нужны параметры конструктора.