È davvero una cattiva pratica prendere in giro un POJO (oggetto valore) se non ti interessa cosa contiene?

1

Nel consiglio di Mockito su come scrivere buoni test, è scritto che non dovremmo prendere in giro oggetti di valore ( link ). Dicono anche

Because instantiating the object is too painful !? => not a valid reason.

Ma non capisco. E se davvero non ci importa cosa contenga quell'oggetto? Diciamo solo che ho una classe di oggetti valore "Foo" che non ha costruttori semplici (ha un costruttore di tutti gli argomenti e contiene oggetti che hanno anche bisogno di costruttori all-args). Diciamo anche che ho il seguente servizio:

public class MyService {
    private SomeService someService;
    private OtherService otherService;

    public void myAwesomeMethod() {
        Foo foo = someService.getFoo();
        someOtherService.doStuff(foo);
    }
}

E ho bisogno di testare quel metodo. Chiaramente qui non mi interessa cosa contenga foo perché lo passo semplicemente a un altro metodo. Se voglio testarlo, dovrei davvero creare un oggetto foo con costruttori / builder nidificati? Per me sarebbe davvero facile prendere in giro foo anche se si tratta di un POJO:

public class MyServiceTest {
    @InjectMocks
    MyService myService;

    @Mock
    private SomeService someService;

    @Mock
    private OtherService otherService;

    @Test
    public void testMyAwesomeMethod() {
        Foo mockedFoo = Mockito.mock(Foo.class);

        Mockito.when(someService.getFoo()).thenReturn(mockedFoo);

        Mockito.verify(otherService).doStuff(mockedFoo);
    }

}

Ma allora non rispetterebbe le linee guida date da Mockito. C'è un'altra alternativa?

    
posta Ricola 29.12.2017 - 15:38
fonte

4 risposte

5

Gli stub (che hai definito mock nella tua domanda) esistono per una ragione: consentono di sostituire il codice aziendale dalle classi dipendono dal metodo che stai testando con alcune affermazioni molto basilari che forniscono dati sufficienti per il metodo testato I mazzi, proprio come gli stub, sostituiscono la logica di business con la logica personalizzata richiesta per i test.

  • Se un POJO non ha codice business (cioè se è anche un oggetto dati), stub e mock sarebbero inutili. L'utilizzo di tali oggetti dati nei test dovrebbe essere abbastanza semplice. Se il metodo richiede un oggetto dati in input, è sufficiente crearne uno nel test.

    Alcuni oggetti dati potrebbero essere molto grandi e contenere dozzine di proprietà. In alcune lingue, è possibile inizializzare parzialmente un oggetto dati impostando solo le proprietà necessarie durante il test; in altre lingue, è necessario inizializzarlo completamente. Se questo è il tuo caso, creando un semplice metodo statico che crea l'oggetto dati con i valori predefiniti e riutilizzando questo metodo in tutti i test potrebbe essere una soluzione.

  • Se un POJO contiene codice aziendale, dovresti isolare i tuoi test di unità da esso, come faresti per qualsiasi altra classe. Qui, stub e mock vanno bene, e non fa differenza se l'oggetto deriso è un POJO o qualcos'altro.

risposta data 29.12.2017 - 17:08
fonte
3

La migliore linea dal tuo link secondo me è

Don't mock everything, it's an anti-pattern

If everything is mocked, are we really testing the production code? Don't hesitate to not mock!

Di solito vuoi testare il tuo codice con input reali e output attesi e solitamente questi saranno strutture dati o pojos / pocos

es.

actual = myfunc(input)
Assert.AreEqual(actual, expected)

Non ha senso prendere in giro quell'input o output perché è stato impostato appositamente per quel test. Ne hai sempre a cuore.

Nel tuo esempio non hai input, output o effetti logici da eseguire. Anche se hai un esempio reale in cui il codice passa semplicemente un oggetto da un servizio a un altro, sicuramente vuoi testare casi limite come null o un oggetto molto grande? dovresti preoccuparti di cosa sia quell'oggetto.

    
risposta data 29.12.2017 - 20:16
fonte
2

Potresti incappare in un odore di codice.

Clearly here I don't care about what foo contains because I just pass it to another method.

La cosa che mi distingue in questo esempio è che al tuo codice non interessa nemmeno che l'oggetto che viene scambiato sia un Foo .

public class MyService<T> {
    private java.util.function.Supplier<? extends T> someService;
    private java.util.function.Consumer<? super T> otherService;

    public void myAwesomeMethod() {
        T foo = someService.get();
        someOtherService.accept(foo);
    }
}

Quindi, nel tuo test unitario, invece di provare a utilizzare Foo , o di creare un doppio test che puoi utilizzare al posto di Foo , puoi inserire una coppia fornitore / consumatore.

Let's just say that I have a value object class "Foo" that doesn't have simple constructors (it has an all-args constructor and contains objects that also need all-args constructors).

Un altro schema comune è quello di utilizzare build data builder per crea i valori che ti servono. Generalmente i builder di test utilizzano interfacce fluenti per comunicare all'interno di un test quali dettagli dell'oggetto sono importante nel contesto del test .

@Test
public void testMyAwesomeMethod() {
    Foo mockedFoo = new FooBuilder().anyOldFoo();

    Mockito.when(someService.getFoo()).thenReturn(mockedFoo);

    Mockito.verify(otherService).doStuff(mockedFoo);
}

Qui, l'interazione con il costruttore spiega che il comportamento di questo test non deve dipendere dal valore foo specifico.

Se strizzi, potresti vedere che il test sta asserendo una proprietà

public void testMyAwesomeMethod() {
    for (Foo candidate : allPossibleFoo()) {
        Mockito.when(someService.getFoo()).thenReturn(candidate);
        // ...
    }
}

Nessuno di questi approcci di builder risolve direttamente il problema di invocare il costruttore di folli; tutto ciò che fa il costruttore è spostare tale complessità dal test .

    
risposta data 31.12.2017 - 14:20
fonte
1

Sei un po 'corretto.

Quando prendi in giro un oggetto, di solito estrai i metodi per restituire valori predefiniti. Ma non c'è davvero una buona ragione per farlo per un oggetto valore, basta usare invece l'oggetto valore.

Tuttavia, in questo particolare scenario non stai eliminando nessuno dei metodi. Stai solo verificando che lo stesso oggetto restituito da una funzione venga passato a un'altra. Quindi non hai gli svantaggi di prendere in giro l'oggetto valore.

Tuttavia, quasi certamente dovresti avere un codice di prova che costruisca un Foo. Il test per SomeService dovrebbe probabilmente assertEquals il risultato di getFoo con qualche Foo costruito. Il test per OtherService deve passare un oggetto Foo costruito a doStuff . Devi avere un codice da qualche parte che costruisca oggetti Foo per quei test, quindi dovresti essere in grado di rifoderare prontamente quel codice di costruzione in un posto in cui può essere riutilizzato.

Tuttavia, in generale, è sospetto che MyService non voglia chiamare alcun metodo su Foo. Questo è spesso un segno che MyService non dovrebbe occuparsi affatto di Foo. Se tutto MyService prende un oggetto da SomeService e lo passa a OtherService qual è il punto di MyService ? Semplifica il tuo codice e collega direttamente i due servizi.

    
risposta data 30.12.2017 - 07:07
fonte

Leggi altre domande sui tag