Come testare la classe astratta in Java con JUnit?


87

Sono nuovo ai test Java con JUnit. Devo lavorare con Java e vorrei utilizzare gli unit test.

Il mio problema è: ho una classe astratta con alcuni metodi astratti. Ma ci sono alcuni metodi che non sono astratti. Come posso testare questa classe con JUnit? Codice di esempio (molto semplice):

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

Voglio testare getSpeed()e getFuel()funzioni.

Una domanda simile a questo problema è qui , ma non sta usando JUnit.

Nella sezione FAQ di JUnit, ho trovato questo collegamento , ma non capisco cosa voglia dire l'autore con questo esempio. Cosa significa questa riga di codice?

public abstract Source getSource() ;

4
Vedi stackoverflow.com/questions/1087339/… per due soluzioni che utilizzano Mockito.
gg

C'è qualche vantaggio nell'apprendere un altro framework per i test? Mockito è solo un'estensione di jUnit o un progetto completamente diverso?
vasco

Mockito non sostituisce JUnit. Come altri framework di mocking, viene utilizzato in aggiunta a un framework di unit test e ti aiuta a creare oggetti fittizi da usare nei tuoi casi di test.
gg

Risposte:


105

Se non hai implementazioni concrete della classe e i metodi no static testarli? Se hai una classe concreta, testerai quei metodi come parte dell'API pubblica della classe concreta.

So cosa stai pensando "Non voglio testare questi metodi più e più volte questo è il motivo per cui ho creato la classe astratta", ma la mia controargomentazione è che lo scopo degli unit test è consentire agli sviluppatori di apportare modifiche, eseguire i test e analizzare i risultati. Parte di questi cambiamenti potrebbe includere l'override dei metodi della tua classe astratta, sia protectede public, che potrebbe portare a cambiamenti comportamentali fondamentali. A seconda della natura di tali modifiche, potrebbe influire sul modo in cui l'applicazione viene eseguita in modi imprevisti, forse negativi. Se si dispone di una buona suite di unit test, i problemi derivanti da questi tipi di modifiche dovrebbero essere evidenti in fase di sviluppo.


17
La copertura del codice al 100% è un mito. Dovresti avere esattamente abbastanza test per coprire tutte le tue ipotesi note su come dovrebbe comportarsi la tua applicazione (preferibilmente scritte prima di scrivere il codice la Test Driven Development). Attualmente sto lavorando su un team TDD molto funzionante e abbiamo solo il 63% di copertura a partire dalla nostra ultima build, tutto scritto durante lo sviluppo. Quello è buono? chissà ?, ma lo considererei una perdita di tempo tornare indietro e provare a spingerlo più in alto.
nsfyn55

3
sicuro. Alcuni sostengono che sia una violazione del buon TDD. Immagina di essere in una squadra. Partite dal presupposto che il metodo sia definitivo e non mettete test per nessuna implementazione concreta. Qualcuno rimuove il modificatore e apporta modifiche che si estendono su un intero ramo della gerarchia di ereditarietà. Non vorresti che la tua suite di test lo rilevasse?
nsfyn55

31
Non sono d'accordo. Che tu lavori in TDD o meno, il metodo concreto della tua classe astratta contiene codice, quindi dovrebbero avere dei test (indipendentemente dal fatto che ci siano o meno sottoclassi). Inoltre, unit test in Java verifica (normalmente) le classi. Pertanto, non c'è davvero alcuna logica nel testare metodi che non fanno parte della classe, ma piuttosto della sua super classe. Seguendo questa logica, non dovremmo testare nessuna classe in Java, ad eccezione delle classi senza alcuna sottoclasse. Per quanto riguarda i metodi che vengono sovrascritti, è esattamente quando aggiungi un test per verificare le modifiche / aggiunte al test della sottoclasse.
Ethanfar

3
@ nsfyn55 E se i metodi concreti fossero final? Non vedo un motivo per testare lo stesso metodo più volte se l'implementazione non può cambiare
Dioxin

3
Non dovremmo avere i test come target dell'interfaccia astratta in modo da poterli eseguire per tutte le implementazioni? Se non fosse possibile, violeremmo quello di Liskov, che vorremmo conoscere e correggere. Solo se l'implementazione aggiunge alcune funzionalità estese (compatibili) dovremmo avere uno specifico unit test per esso (e solo per quella funzionalità extra).
TNE

36

Creare una classe concreta che erediti la classe astratta e quindi testare le funzioni che la classe concreta eredita dalla classe astratta.


Cosa faresti se avessi 10 classi concrete che estendono la classe astratta e ognuna di queste classi concrete implementerebbe solo 1 metodo e diciamo che gli altri 2 metodi sono gli stessi per ciascuna di queste classi, perché sono implementati in astratto classe? Il mio caso è che non voglio copiare e incollare i test per la classe astratta in ciascuna sottoclasse.
scarface

12

Con la classe di esempio che hai pubblicato non sembra avere molto senso testare getFuel()egetSpeed() dal momento che possono restituire solo 0 (non ci sono setter).

Tuttavia, supponendo che questo fosse solo un esempio semplificato a scopo illustrativo e che tu abbia motivi legittimi per testare metodi nella classe base astratta (altri hanno già sottolineato le implicazioni), potresti impostare il tuo codice di test in modo che crei un anonimo sottoclasse della classe base che fornisce solo implementazioni fittizie (non opzionali) per i metodi astratti.

Ad esempio, nel tuo TestCasepotresti fare questo:

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

Quindi prova il resto dei metodi, ad esempio:

public class CarTest extends TestCase
{
    private Car c;

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

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

    [...]
}

(Questo esempio è basato sulla sintassi JUnit3. Per JUnit4, il codice sarebbe leggermente diverso, ma l'idea è la stessa.)


Grazie per la risposta. Sì, il mio esempio è stato semplificato (e non così buono). Dopo aver letto tutte le risposte qui, ho scritto una lezione fittizia. Ma come ha scritto @ nsfyn55 nella sua risposta, scrivo test per ogni discendente di questa classe astratta.
vasco

9

Se hai comunque bisogno di una soluzione (ad esempio perché hai troppe implementazioni della classe astratta e il test ripeterà sempre le stesse procedure) allora potresti creare una classe di test astratta con un metodo factory astratto che sarà eccitato dall'implementazione di quello classe di prova. Questo esempio funziona o me con TestNG:

La classe di prova astratta di 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);
    ...

Implementazione di Car

class ElectricCar extends Car {

    private final int batteryCapacity;

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

    ...

Classe ElectricCarTestdi unit test della Classe 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
    ...

5

Potresti fare qualcosa del genere

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

4

Creerei una classe interna jUnit che eredita dalla classe astratta. Questo può essere istanziato e avere accesso a tutti i metodi definiti nella classe astratta.

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


class ConcreteClass extends AbstractClass {

}

3
Questo è un ottimo consiglio. Tuttavia, potrebbe essere migliorato fornendo un esempio. Forse un esempio della classe che stai descrivendo.
SDJMcHattie

2

È possibile creare un'istanza di una classe anonima e quindi testarla.

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

Tieni presente che la visibilità deve essere protectedper la proprietà myDependencyServicedella classe astratta ClassUnderTest.

Puoi anche combinare questo approccio ordinatamente con Mockito. Vedi qui .


2

Il mio modo di testarlo è abbastanza semplice, all'interno di ciascuno abstractUnitTest.java. Creo semplicemente una classe in abstractUnitTest.java che estenda la classe astratta. E provalo in questo modo.


0

Non puoi testare l'intera classe astratta. In questo caso hai metodi astratti, questo significa che dovrebbero essere implementati da classi che estendono una data classe astratta.

In quella classe il programmatore deve scrivere il codice sorgente che è dedicato alla sua logica.

In altre parole, non si ha la sensazione di testare la classe astratta perché non si è in grado di verificarne il comportamento finale.

Se hai funzionalità importanti non correlate ai metodi astratti in qualche classe astratta, crea semplicemente un'altra classe in cui il metodo astratto genererà qualche eccezione.


0

Come opzione, puoi creare una classe di test astratta che copre la logica all'interno della classe astratta ed estenderla per ogni test di sottoclasse. In questo modo puoi assicurarti che questa logica venga testata separatamente per ogni bambino.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.