test classes java unit-testing testing abstract-class

classes - test abstract class java



¿Cómo hacer pruebas unitarias de clases abstractas: extender con talones? (14)

Me preguntaba cómo hacer una prueba unitaria de las clases abstractas y las clases que extienden las clases abstractas.

¿Debo probar la clase abstracta extendiéndolo, eliminando los métodos abstractos y luego probando todos los métodos concretos? Entonces, ¿solo probar los métodos que anulo y probar los métodos abstractos en las pruebas unitarias para objetos que extienden mi clase abstracta?

¿Debo tener un caso de prueba abstracto que pueda usarse para probar los métodos de la clase abstracta y extender esta clase en mi caso de prueba para los objetos que extienden la clase abstracta?

Tenga en cuenta que mi clase abstracta tiene algunos métodos concretos.


Escribe un objeto simulado y úsalo solo para probar. Por lo general, son muy, muy mínimos (heredados de la clase abstracta) y no más. Luego, en su Prueba de unidad puede llamar al método abstracto que desea probar.

Debería probar la clase abstracta que contenga alguna lógica como todas las demás clases que tiene.


Este es el patrón que suelo seguir cuando configuro un arnés para probar una clase abstracta:

public abstract class MyBase{ /*...*/ public abstract void VoidMethod(object param1); public abstract object MethodWithReturn(object param1); /*,,,*/ }

Y la versión que uso bajo prueba:

public class MyBaseHarness : MyBase{ /*...*/ public Action<object> VoidMethodFunction; public override void VoidMethod(object param1){ VoidMethodFunction(param1); } public Func<object, object> MethodWithReturnFunction; public override object MethodWithReturn(object param1){ return MethodWihtReturnFunction(param1); } /*,,,*/ }

Si se llaman los métodos abstractos cuando no lo espero, las pruebas fallan. Al organizar las pruebas, puedo fácilmente apagar los métodos abstractos con lambdas que realizan afirmaciones, emiten excepciones, devuelven valores diferentes, etc.


Hay dos formas en que se utilizan las clases base abstractas.

  1. Está especializando su objeto abstracto, pero todos los clientes usarán la clase derivada a través de su interfaz base.

  2. Está utilizando una clase base abstracta para eliminar la duplicación dentro de los objetos en su diseño, y los clientes usan las implementaciones concretas a través de sus propias interfaces.

Solución para 1 - Patrón de estrategia

Si tiene la primera situación, entonces realmente tiene una interfaz definida por los métodos virtuales en la clase abstracta que están implementando sus clases derivadas.

Debería considerar hacer de esto una interfaz real, cambiar su clase abstracta para que sea concreta, y tomar una instancia de esta interfaz en su constructor. Sus clases derivadas se convierten en implementaciones de esta nueva interfaz.

Esto significa que ahora puede probar su clase abstracta anterior utilizando una instancia simulada de la nueva interfaz, y cada nueva implementación a través de la interfaz ahora pública. Todo es simple y comprobable.

Solución para 2

Si tiene la segunda situación, entonces su clase abstracta está funcionando como una clase auxiliar.

Echa un vistazo a la funcionalidad que contiene. Vea si alguno de ellos puede insertarse en los objetos que se están manipulando para minimizar esta duplicación. Si aún le queda algo, considere convertirlo en una clase auxiliar que su implementación concreta tome en su constructor y elimine su clase base.

Esto conduce nuevamente a clases concretas que son simples y fáciles de probar.

Como una regla

Favorece la red compleja de objetos simples sobre una red simple de objetos complejos.

La clave para el código extensible comprobable son los bloques de construcción pequeños y el cableado independiente.

Actualizado: ¿Cómo manejar mezclas de ambos?

Es posible tener una clase base que realice estos dos roles ... es decir: tiene una interfaz pública y tiene métodos de ayuda protegidos. Si este es el caso, entonces puede factorizar los métodos de ayuda en una clase (escenario 2) y convertir el árbol de herencia en un patrón de estrategia.

Si descubre que tiene algunos métodos que implementa su clase base directamente y otros son virtuales, aún puede convertir el árbol de herencia en un patrón de estrategia, pero también lo tomaría como un buen indicador de que las responsabilidades no están alineadas correctamente, y puede que Necesita refactorización.

Actualización 2: Clases abstractas como un trampolín (2014/06/12)

Tuve una situación el otro día donde usé el resumen, así que me gustaría explorar por qué.

Tenemos un formato estándar para nuestros archivos de configuración. Esta herramienta en particular tiene 3 archivos de configuración, todos en ese formato. Quería una clase fuertemente tipada para cada archivo de configuración, por lo que, a través de la inyección de dependencia, una clase podría solicitar la configuración que le importaba.

Implementé esto al tener una clase base abstracta que sabe cómo analizar los formatos de los archivos de configuración y las clases derivadas que expusieron esos mismos métodos, pero que encapsularon la ubicación del archivo de configuración.

Podría haber escrito un "SettingsFileParser" que envolvieron las 3 clases, y luego delegarlo a la clase base para exponer los métodos de acceso a los datos. Elegí no hacer esto todavía, ya que daría lugar a 3 clases derivadas con más código de delegación que cualquier otra cosa.

Sin embargo ... a medida que este código evoluciona y los consumidores de cada una de estas clases de configuración se vuelven más claros. Los usuarios de cada configuración solicitarán algunas configuraciones y las transformarán de alguna manera (ya que las configuraciones son texto, pueden envolverlas en objetos para convertirlas en números, etc.). A medida que esto suceda, comenzaré a extraer esta lógica en los métodos de manipulación de datos y los devolveré a las clases de configuración fuertemente tipadas. Esto conducirá a una interfaz de nivel superior para cada conjunto de configuraciones, que eventualmente ya no es consciente de que se trata de ''configuraciones''.

En este punto, las clases de configuración fuertemente tipificadas ya no necesitarán los métodos "getter" que exponen la implementación de la "configuración" subyacente.

En ese momento ya no desearía que su interfaz pública incluyera los métodos de acceso a la configuración; así que cambiaré esta clase para encapsular una clase de analizador de configuraciones en lugar de derivar de ella.

Por lo tanto, la clase de resumen es: una forma de evitar el código de delegación en este momento y un marcador en el código para recordarme que cambie el diseño más adelante. Puede que nunca llegue a eso, por lo que puede vivir un buen rato ... solo el código puede decirlo.

Encuentro que esto es cierto con cualquier regla ... como "no hay métodos estáticos" o "no hay métodos privados". Indican un olor en el código ... y eso es bueno. Lo mantiene buscando la abstracción que ha perdido ... y le permite continuar brindando valor a su cliente mientras tanto.

Me imagino reglas como esta definiendo un paisaje, donde el código mantenible vive en los valles. A medida que agrega un nuevo comportamiento, es como si cayera la lluvia en su código. Inicialmente lo pones donde sea que caiga ... luego refactorizas para permitir que las fuerzas del buen diseño empujen el comportamiento hasta que todo termine en los valles.


Lo que hago para las clases e interfaces abstractas es lo siguiente: Escribo una prueba, que usa el objeto como es concreto. Pero la variable de tipo X (X es la clase abstracta) no se establece en la prueba. Esta clase de prueba no se agrega a la serie de pruebas, sino a sus subclases, que tienen un método de configuración que establece la variable en una implementación concreta de X. De esa manera no duplico el código de prueba. Las subclases de la prueba no utilizada pueden agregar más métodos de prueba si es necesario.


Para realizar una prueba de unidad específicamente en la clase abstracta, debe derivarla para fines de prueba, probar los resultados de base.method () y el comportamiento deseado al heredar.

Usted prueba un método llamándolo así que pruebe una clase abstracta implementándolo ...


Primero, si la clase abstracta contenía algún método concreto, creo que deberías hacer esto considerando este ejemplo.

public abstract class A { public boolean method 1 { // concrete method which we have to test. } } class B extends class A { @override public boolean method 1 { // override same method as above. } } class Test_A { private static B b; // reference object of the class B @Before public void init() { b = new B (); } @Test public void Test_method 1 { b.method 1; // use some assertion statements. } }


Si los métodos concretos invocan alguno de los métodos abstractos que la estrategia no funcionará, usted querría probar el comportamiento de cada clase secundaria por separado. De lo contrario, extenderlo y eliminar los métodos abstractos como ha descrito debería estar bien, nuevamente, siempre que los métodos concretos de la clase abstracta se desacoplen de las clases secundarias.


Si su clase abstracta contiene una funcionalidad concreta que tiene un valor comercial, por lo general la probaré directamente creando un doble de prueba que elimine los datos abstractos o utilizando un marco de simulacro para hacer esto por mí. Cuál elijo depende mucho de si necesito escribir implementaciones específicas de prueba de los métodos abstractos o no.

El escenario más común en el que necesito hacer esto es cuando estoy usando el patrón de Método de plantilla , como cuando estoy creando algún tipo de marco extensible que será utilizado por un tercero. En este caso, la clase abstracta es lo que define el algoritmo que quiero probar, por lo que tiene más sentido probar la base abstracta que una implementación específica.

Sin embargo, creo que es importante que estas pruebas se centren solo en implementaciones concretas de lógica empresarial real ; no debe hacer una prueba unitaria de los detalles de implementación de la clase abstracta porque terminará con pruebas frágiles.


Si una clase abstracta es apropiada para su implementación, pruebe (como se sugirió anteriormente) una clase concreta derivada. Tus suposiciones son correctas.

Para evitar confusiones futuras, tenga en cuenta que esta clase de prueba concreta no es un simulacro, sino una falsificación .

En términos estrictos, un simulacro se define por las siguientes características:

  • Se utiliza un simulacro en lugar de todas y cada una de las dependencias de la clase de materia que se está probando.
  • Un simulacro es una pseudo-implementación de una interfaz (puede recordar que, como regla general, las dependencias deben declararse como interfaces; la capacidad de prueba es una de las razones principales para esto)
  • Los comportamientos de los miembros de la interfaz del simulacro, ya sean métodos o propiedades, se suministran en el momento de la prueba (nuevamente, mediante el uso de un marco de simulacros). De esta manera, evita la conexión de la implementación que se está probando con la implementación de sus dependencias (que deberían tener sus propias pruebas discretas).

Siguiendo la respuesta de @ patrick-desjardins, implementé el resumen y su clase de implementación junto con @Test siguiente manera:

Abstarct class - ABC.java

import java.util.ArrayList; import java.util.List; public abstract class ABC { abstract String sayHello(); public List<String> getList() { final List<String> defaultList = new ArrayList<>(); defaultList.add("abstract class"); return defaultList; } }

Como las clases Abstractas no pueden ser instanciadas, pero pueden ser subclasificadas , la clase concreta DEF.java es la siguiente:

public class DEF extends ABC { @Override public String sayHello() { return "Hello!"; } }

@Test clase para probar tanto el método abstracto como el no abstracto:

import org.junit.Before; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.contains; import java.util.Collection; import java.util.List; import static org.hamcrest.Matchers.equalTo; import org.junit.Test; public class DEFTest { private DEF def; @Before public void setup() { def = new DEF(); } @Test public void add(){ String result = def.sayHello(); assertThat(result, is(equalTo("Hello!"))); } @Test public void getList(){ List<String> result = def.getList(); assertThat((Collection<String>) result, is(not(empty()))); assertThat(result, contains("abstract class")); } }


Supongo que podría querer probar la funcionalidad básica de una clase abstracta ... Pero probablemente sería mejor que extienda la clase sin anular ningún método, y haga burla con el mínimo esfuerzo por los métodos abstractos.


Una de las principales motivaciones para usar una clase abstracta es habilitar el polimorfismo dentro de su aplicación, es decir, puede sustituir una versión diferente en tiempo de ejecución. De hecho, esto es casi lo mismo que usar una interfaz, excepto que la clase abstracta proporciona una plomería común, a menudo denominada patrón de plantilla .

Desde la perspectiva de las pruebas unitarias, hay dos cosas a considerar:

  1. Interacción de su clase abstracta con las clases relacionadas . El uso de un marco de prueba simulado es ideal para este escenario, ya que muestra que su clase abstracta juega bien con los demás.

  2. Funcionalidad de las clases derivadas . Si tiene lógica personalizada que ha escrito para sus clases derivadas, debe probar esas clases de forma aislada.

edit: RhinoMocks es un increíble marco de prueba simulado que puede generar objetos simulados en tiempo de ejecución al derivarse dinámicamente de su clase. Este enfoque puede ahorrarle innumerables horas de clases derivadas de codificación manual.


Yo argumentaría en contra de las pruebas "abstractas". Creo que una prueba es una idea concreta y no tiene una abstracción. Si tiene elementos comunes, póngalos en métodos de ayuda o clases para que todos puedan usar.

En cuanto a la prueba de una clase de prueba abstracta, asegúrese de preguntarse qué es lo que está probando. Hay varios enfoques, y debe averiguar qué funciona en su escenario. ¿Estás tratando de probar un nuevo método en tu subclase? Luego haga que sus pruebas solo interactúen con ese método. ¿Estás probando los métodos en tu clase base? Entonces, probablemente tenga un accesorio separado solo para esa clase y pruebe cada método individualmente con tantas pruebas como sea necesario.


una forma es escribir un caso de prueba abstracto que corresponda a su clase abstracta, luego escribir casos de prueba concretos que subclasificar su caso de prueba abstracto. haga esto para cada subclase concreta de su clase abstracta original (es decir, la jerarquía de su caso de prueba refleja su jerarquía de clase). Consulte Prueba de una interfaz en el libro de recetas de junit: http://safari.informit.com/9781932394238/ch02lev1sec6 .

También vea Testcase Superclass en patrones xUnit: http://xunitpatterns.com/Testcase%20Superclass.html