¿Cómo probar la clase abstracta en Java con JUnit?

87

Soy nuevo en las pruebas de Java con JUnit. Tengo que trabajar con Java y me gustaría usar pruebas unitarias.

Mi problema es: tengo una clase abstracta con algunos métodos abstractos. Pero hay algunos métodos que no son abstractos. ¿Cómo puedo probar esta clase con JUnit? Código de ejemplo (muy simple):

abstract class Car {

    public Car(int speed, int fuel) {
        this.speed = speed;
        this.fuel = fuel;
    }

    private int speed;
    private int fuel;

    abstract void drive();

    public int getSpeed() {
        return this.speed;
    }

    public int getFuel() {
        return this.fuel;
    }
}

Quiero probar getSpeed()y getFuel()funciones.

Aquí hay una pregunta similar a este problema , pero no está usando JUnit.

En la sección de preguntas frecuentes de JUnit, encontré este enlace , pero no entiendo lo que el autor quiere decir con este ejemplo. ¿Qué significa esta línea de código?

public abstract Source getSource() ;
vasco
fuente
4
Consulte stackoverflow.com/questions/1087339/… para ver dos soluciones con Mockito.
ddso
¿Existe alguna ventaja para aprender otro marco de pruebas? ¿Mockito es solo una extensión de jUnit o un proyecto completamente diferente?
vasco
Mockito no reemplaza a JUnit. Al igual que otros marcos de simulación, se utiliza además de un marco de pruebas unitarias y le ayuda a crear objetos de simulación para usar en sus casos de prueba.
ddso
1
Agnóstico del

Respuestas:

104

Si no tiene implementaciones concretas de la clase y los métodos no son, static¿cuál es el punto de probarlos? Si tiene una clase concreta, probará esos métodos como parte de la API pública de la clase concreta.

Sé lo que estás pensando "No quiero probar estos métodos una y otra vez, esa es la razón por la que creé la clase abstracta", pero mi argumento en contra es que el objetivo de las pruebas unitarias es permitir que los desarrolladores realicen cambios, ejecutar las pruebas y analizar los resultados. Parte de esos cambios podrían incluir anular los métodos de su clase abstracta, tanto protectedy public, lo que podría resultar en cambios fundamentales de comportamiento. Dependiendo de la naturaleza de esos cambios, podría afectar la forma en que se ejecuta su aplicación de formas inesperadas, posiblemente negativas. Si tiene un buen conjunto de pruebas unitarias, los problemas derivados de estos tipos de cambios deberían ser evidentes en el momento del desarrollo.

nsfyn55
fuente
17
La cobertura del código al 100% es un mito. Debería tener exactamente suficientes pruebas para cubrir todas sus hipótesis conocidas sobre cómo debería comportarse su aplicación (preferiblemente escrito antes de escribir el código, Test Driven Development). Actualmente estoy trabajando en un equipo TDD muy funcional y solo tenemos un 63% de cobertura desde nuestra última compilación, todo escrito a medida que lo desarrollamos. ¿Es bueno eso? ¿quién sabe ?, pero yo consideraría una pérdida de tiempo volver atrás y tratar de aumentarlo más.
nsfyn55
3
Por supuesto. Algunos dirían que es una violación de una buena TDD. Imagina que estás en un equipo. Usted asume que el método es final y no pone pruebas en ninguna implementación concreta. Alguien elimina el modificador y realiza cambios que se extienden por toda una rama de la jerarquía de herencia. ¿No le gustaría que su suite de pruebas lo captara?
nsfyn55
31
Estoy en desacuerdo. Ya sea que trabaje en TDD o no, el método concreto de su clase abstracta contiene código, por lo tanto, deben tener pruebas (independientemente de si hay subclases o no). Además, las pruebas unitarias en Java prueban (normalmente) clases. Por lo tanto, realmente no hay lógica en los métodos de prueba que no son parte de la clase, sino de su superclase. Siguiendo esa lógica, no deberíamos probar ninguna clase en Java, excepto las clases sin subclases en absoluto. Con respecto a los métodos que se anulan, es exactamente cuando agrega una prueba para verificar los cambios / adiciones a la prueba de la subclase.
ethanfar
3
@ nsfyn55 ¿Y si los métodos concretos fueran final? No veo una razón para probar el mismo método varias veces si la implementación no puede cambiar
Dioxina
3
¿No deberíamos tener las pruebas dirigidas a la interfaz abstracta para poder ejecutarlas para todas las implementaciones? Si no es posible, estaríamos violando la de Liskov, que nos gustaría conocer y corregir. Solo si la implementación agrega alguna funcionalidad extendida (compatible) deberíamos tener una prueba unitaria específica para ella (y solo para esa funcionalidad adicional).
TNE
36

Cree una clase concreta que herede la clase abstracta y luego pruebe las funciones que la clase concreta hereda de la clase abstracta.

Kevin Bowersox
fuente
¿Qué haría en caso de que tenga 10 clases concretas que extienden la clase abstracta y cada una de estas clases concretas implementaría solo 1 método y digamos que otros 2 métodos son iguales para cada una de estas clases, porque se implementan en abstracto? ¿clase? Mi caso es que no quiero copiar y pegar las pruebas para la clase abstracta en cada subclase.
Scarface
12

Con la clase de ejemplo que publicó, no parece tener mucho sentido probar getFuel()y getSpeed()dado que solo pueden devolver 0 (no hay establecedores).

Sin embargo, suponiendo que este sea solo un ejemplo simplificado con fines ilustrativos, y que tenga razones legítimas para probar métodos en la clase base abstracta (otros ya han señalado las implicaciones), puede configurar su código de prueba para que cree un anónimo subclase de la clase base que solo proporciona implementaciones ficticias (no operativas) para los métodos abstractos.

Por ejemplo, en tu TestCasepodrías hacer esto:

c = new Car() {
       void drive() { };
   };

Luego pruebe el resto de los métodos, por ejemplo:

public class CarTest extends TestCase
{
    private Car c;

    public void setUp()
    {
        c = new Car() {
            void drive() { };
        };
    }

    public void testGetFuel() 
    {
        assertEquals(c.getFuel(), 0);
    }

    [...]
}

(Este ejemplo se basa en la sintaxis de JUnit3. Para JUnit4, el código sería ligeramente diferente, pero la idea es la misma).

Grodriguez
fuente
Gracias por responder. Sí, mi ejemplo fue simplificado (y no tan bueno). Después de leer todas las respuestas aquí, escribí una clase ficticia. Pero como escribió @ nsfyn55 en su respuesta, escribo una prueba para cada descendiente de esta clase abstracta.
vasco
9

Si necesita una solución de todos modos (por ejemplo, porque tiene demasiadas implementaciones de la clase abstracta y las pruebas siempre repetirían los mismos procedimientos), entonces podría crear una clase de prueba abstracta con un método de fábrica abstracto que será ejecutado por la implementación de ese clase de prueba. Este ejemplo funciona para mí con TestNG:

La clase de prueba abstracta de Car:

abstract class CarTest {

// the factory method
abstract Car createCar(int speed, int fuel);

// all test methods need to make use of the factory method to create the instance of a car
@Test
public void testGetSpeed() {
    Car car = createCar(33, 44);
    assertEquals(car.getSpeed(), 33);
    ...

Implementación de Car

class ElectricCar extends Car {

    private final int batteryCapacity;

    public ElectricCar(int speed, int fuel, int batteryCapacity) {
        super(speed, fuel);
        this.batteryCapacity = batteryCapacity;
    }

    ...

Clase ElectricCarTestde prueba unitaria de la Clase ElectricCar:

class ElectricCarTest extends CarTest {

    // implementation of the abstract factory method
    Car createCar(int speed, int fuel) {
        return new ElectricCar(speed, fuel, 0);
    }

    // here you cann add specific test methods
    ...
thomas.mc.work
fuente
5

Podrías hacer algo como esto

public abstract MyAbstractClass {

    @Autowire
    private MyMock myMock;        

    protected String sayHello() {
            return myMock.getHello() + ", " + getName();
    }

    public abstract String getName();
}

// this is your JUnit test
public class MyAbstractClassTest extends MyAbstractClass {

    @Mock
    private MyMock myMock;

    @InjectMocks
    private MyAbstractClass thiz = this;

    private String myName = null;

    @Override
    public String getName() {
        return myName;
    }

    @Test
    public void testSayHello() {
        myName = "Johnny"
        when(myMock.getHello()).thenReturn("Hello");
        String result = sayHello();
        assertEquals("Hello, Johnny", result);
    }
}
iil
fuente
4

Crearía una clase interna jUnit que hereda de la clase abstracta. Esto se puede instanciar y tener acceso a todos los métodos definidos en la clase abstracta.

public class AbstractClassTest {
   public void testMethod() {
   ...
   }
}


class ConcreteClass extends AbstractClass {

}
marting
fuente
3
Este es un excelente consejo. Sin embargo, podría mejorarse proporcionando un ejemplo. Quizás un ejemplo de la clase que estás describiendo.
SDJMcHattie
2

Puede crear una instancia de una clase anónima y luego probar esa clase.

public class ClassUnderTest_Test {

    private ClassUnderTest classUnderTest;

    private MyDependencyService myDependencyService;

    @Before
    public void setUp() throws Exception {
        this.myDependencyService = new MyDependencyService();
        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 debe ser protectedpara la propiedad myDependencyServicede la clase abstracta ClassUnderTest.

También puede combinar este enfoque perfectamente con Mockito. Vea aquí .

Samuel
fuente
2

Mi forma de probar esto es bastante simple, dentro de cada uno abstractUnitTest.java. Simplemente creo una clase en abstractUnitTest.java que amplía la clase abstracta. Y pruébalo de esa manera.

Haomin
fuente
0

No puedes probar toda la clase abstracta. En este caso, tiene métodos abstractos, esto significa que deben ser implementados por una clase que amplíe la clase abstracta dada.

En esa clase el programador tiene que escribir el código fuente que se dedica a su lógica.

En otras palabras, no tiene sentido probar la clase abstracta porque no puede verificar el comportamiento final de la misma.

Si tiene una funcionalidad importante no relacionada con los métodos abstractos en alguna clase abstracta, simplemente cree otra clase donde el método abstracto arrojará alguna excepción.

Damian Leszczyński - Vash
fuente
0

Como opción, puede crear una clase de prueba abstracta que cubra la lógica dentro de la clase abstracta y extenderla para cada prueba de subclase. Para que de esta manera pueda asegurarse de que esta lógica se probará para cada niño por separado.

Andrew Taran
fuente