Pregunta

Me gustaría probar una clase abstracta. Claro, puedo escribir manualmente un simulacro que hereda de la clase.

¿Puedo hacer esto utilizando un marco de burla (estoy usando Mockito) en lugar de la elaboración de la mano de mi maqueta? ¿Cómo?

¿Fue útil?

Solución

La siguiente prueba de que las clases abstractas sin crear un "verdadero" subclase Let sugerencia -. La maqueta es la subclase

uso Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS), a continuación, se burlan ningún métodos abstractos que se invocan.

Ejemplo:

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

Nota: La belleza de esta solución es que no lo hace Tienes para poner en práctica los métodos abstractos, siempre y cuando no se invocan

.

En mi honesta opinión, esto es más limpio que el uso de un espía, ya que un espía requiere una instancia, lo que significa que tiene que crear una subclase instantiatable de la clase abstracta.

Otros consejos

Si sólo tiene que probar algunos de los métodos concretos sin tocar ninguno de los resúmenes, se puede utilizar CALLS_REAL_METHODS (ver Morten respuesta ), pero si el método concreto que se está probando llama a algunos de los resúmenes o métodos de interfaz no implementadas, esto no funcionará - Mockito se quejará "no se puede llamar método real en la interfaz de java ".

(Sí, es un diseño pésimo, pero algunos marcos, por ejemplo, la tapicería 4, tipo de fuerza en usted.)

La solución es revertir este enfoque - utilizar el comportamiento simulacro ordinaria (es decir, todo está burlado / apagó) y el uso doCallRealMethod() llamar explícitamente a cabo el método concreto que se está probando. Por ejemplo.

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

Actualización de añadir:

Para métodos no vacíos, se tendrá que utilizar thenCallRealMethod() en lugar, por ejemplo:

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

De lo contrario Mockito se quejará "tropezar sin terminar detectado."

Esto se puede conseguir mediante el uso de un espía (usar la versión más reciente de Mockito 1.8+ sin embargo).

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

marcos que imita están diseñados para hacer más fácil para burlarse a cabo las dependencias de la clase que se está probando. Cuando se utiliza un marco de burla para burlarse de una clase, la mayoría de los marcos crean dinámicamente una subclase, y reemplazar la implementación del método con el código para la detección cuando se llama a un método y devolver un valor falso.

Cuando se prueba una clase abstracta, que desea ejecutar los métodos no abstractos del tema bajo prueba (SUT), por lo que un marco de burla no es lo que desea.

Parte de la confusión es que la respuesta a la pregunta se ha vinculado a dicho para hacer a mano una maqueta que se extiende desde su clase abstracta. Yo no lo llamaría una clase así una maqueta. Una maqueta es una clase que se utiliza como un reemplazo para una dependencia, está programado con las expectativas, y se puede consultar para ver si se cumplen las expectativas.

En lugar de ello, sugiero que define una subclase no abstracta de la clase abstracta en su prueba. Si eso se traduce en demasiado código, que puede ser una señal de que su clase es difícil extender.

Una solución alternativa sería hacer el caso de test en sí abstracto, con un método abstracto para crear el SUT (en otras palabras, el caso de prueba usaría el Plantilla Método patrón de diseño).

Trate de usar una respuesta personalizada.

Por ejemplo:

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

Se devolverá el simulacro de métodos abstractos y llamará al método real para métodos concretos.

Lo que realmente me hace sentir mal por burlarse de las clases abstractas es el hecho de que ni el constructor por defecto YourAbstractClass () es llamado (super falta () con fingida), ni parece que haya ninguna forma en Mockito por defecto inicializar propiedades simuladas ( por ejemplo, propiedades de lista con ArrayList vacío o LinkedList).

Mi clase abstracta (básicamente el código fuente de la clase se genera) no proporciona una inyección de dependencias colocador de elementos de la lista, ni un constructor inicializa donde los elementos de la lista (que traté de añadir manualmente).

Sólo los atributos de clase de inicialización uso por defecto: Lista DEP1 privada = new ArrayList; DEP2 lista Privada = new ArrayList

Así que no hay manera de burlarse de una clase abstracta sin necesidad de utilizar una implementación de objeto real (por ejemplo la definición de la clase interna en la clase de prueba de unidad, métodos abstractos primordiales) y espiar el objeto real (que hace la inicialización de campo propiamente dicho).

Es una pena que sólo PowerMock ayudaría aquí aún más.

Si se asume sus clases de prueba están en el mismo paquete (bajo una raíz de origen diferente) como sus clases bajo prueba puede simplemente crear la maqueta:

YourClass yourObject = mock(YourClass.class);

y llamar a los métodos que desea probar tal como lo haría cualquier otro método.

Es necesario proporcionar expectativas para cada método que se llama con la expectativa de cualquiera de los métodos concretos de llamar al método súper - no está seguro de cómo se haría eso con Mockito, pero yo creo que es posible con EasyMock

.

Todo esto está haciendo es crear una instancia concreta de YouClass y que le ahorra el esfuerzo de proporcionar implementaciones vacías de cada método abstracto.

Como acotación al margen, a menudo resulta útil implementar la clase abstracta en mi prueba, donde sirve como un ejemplo de implementación que pongo a prueba a través de su interfaz pública, aunque esto depende de la funcionalidad proporcionada por la clase abstracta.

Puede extender la clase abstracta con una clase anónima en su prueba. Por ejemplo (usando Junit 4):

private AbstractClassName classToTest;

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

// Test the AbstractClassName methods.

Puede crear una instancia de una clase anónima, inyectar su burla y luego probar esa clase.

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

Tenga en cuenta que la visibilidad se debe protected para la myDependencyService propiedad del ClassUnderTest clase abstracta.

Whitebox.invokeMethod (..) puede ser útil en este caso.

Mockito permite burlarse de clases abstractas por medio de la anotación @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();
        // ...
    }
}

La desventaja es que no se puede utilizar si necesita parámetros del constructor.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top