Testare classi con Junit e H2

di il
18 risposte

Testare classi con Junit e H2

Salve, argomento completamente nuovo per me e compito da finire entro domani, ma sono in alto mare e nessuno ha esperienza con i test, junit, h2, mockito. Ho visto vari esempi e qualche tutorial, il primo punto che non mi torna:

Ho un servizio che va a prelevari dati dal db, una query che pesca campi da 5 tabelle tra loro in relazione. Nel mio H2 devo ricostruire tutte e 5 le tabelle o ne posso fare una contenete tutti i campi che la query restituisce? perché ricostruire il db la vedo come una operazione lunga e complessa, più tempo per questo test che per scrivere il servizio... C'è una regola fissa da seguire o è a discrezione di chi è al comando?

Altro punto: ho visto che ci sono vari metodi, Assert(), ecc per verificare l'effettiva positività del test. Ma se il mio metodo non restituisce nulla (per scelta, mi è stato detto di dare void come tipo di ritorno a tutti i metodi di insert ed delete), quale metodo devo usare?

grazie

18 Risposte

  • Re: Testare classi con Junit e H2

    WinstonSmith ha scritto:


    Salve, argomento completamente nuovo per me e compito da finire entro domani, ma sono in alto mare e nessuno ha esperienza con i test, junit, h2, mockito.
    Chiariamo subito una cosa: se vuoi fare uno "unit" test, devi innanzitutto stabilire e precisare COSA è per te quella unità (nelle applicazioni con Spring, può essere ad esempio un @Service).
    Quindi puoi certamente testare della logica MA non dovresti mai arrivare alla base dati fisica. Vuol dire che se hai fatto uno strato di "DAO" (ovvero quello che fa materialmente l'accesso al DB) questo strato lo dovresti "staccare" nei test e sostituirlo con qualcosa di fittizio (i mock).
    Ma per fare questo devi aver realizzato l'applicazione in un modo tale che sia "facile" fare questa operazione. Se hai fatto tutto cablato, mescolato insieme (e senza una dependency injection), può risultare difficile/impossibile fare uno unit-test staccando l'accesso al DB.

    Se si vuole, si possono anche fare dei test che vanno materialmente su DB. Non sono più tanto degli "unit" test ma sono degli "integration" test. E devi avere un DB apposito per i test (NON può essere quello che l'applicazione usa realmente) e ad ogni singolo test devi predisporre un apposito setup che imposti la base dati in modo appropriato per i test in generale e/o per quel singolo test specifico. Questo è decisamente più difficile e ci sono anche librerie apposite per questo.

    Quindi dato che hai chiarito poco, inizia a precisare: quali framework/librerie stai usando, come è strutturata la applicazione, se hai usato interfacce di astrazione (specialmente per la parte DAO) ecc... Altrimenti non si può dire facilmente se la tua applicazione è testabile e come ....
  • Re: Testare classi con Junit e H2

    andbin ha scritto:



    Chiariamo subito una cosa: se vuoi fare uno "unit" test, devi innanzitutto stabilire e precisare COSA è per te quella unità (nelle applicazioni con Spring, può essere ad esempio un @Service).
    Quindi puoi certamente testare della logica MA non dovresti mai arrivare alla base dati fisica. Vuol dire che se hai fatto uno strato di "DAO" (ovvero quello che fa materialmente l'accesso al DB) questo strato lo dovresti "staccare" nei test e sostituirlo con qualcosa di fittizio (i mock).
    Ma per fare questo devi aver realizzato l'applicazione in un modo tale che sia "facile" fare questa operazione. Se hai fatto tutto cablato, mescolato insieme (e senza una dependency injection), può risultare difficile/impossibile fare uno unit-test staccando l'accesso al DB.

    Se si vuole, si possono anche fare dei test che vanno materialmente su DB. Non sono più tanto degli "unit" test ma sono degli "integration" test. E devi avere un DB apposito per i test (NON può essere quello che l'applicazione usa realmente) e ad ogni singolo test devi predisporre un apposito setup che imposti la base dati in modo appropriato per i test in generale e/o per quel singolo test specifico. Questo è decisamente più difficile e ci sono anche librerie apposite per questo.

    Quindi dato che hai chiarito poco, inizia a precisare: quali framework/librerie stai usando, come è strutturata la applicazione, se hai usato interfacce di astrazione (specialmente per la parte DAO) ecc... Altrimenti non si può dire facilmente se la tua applicazione è testabile e come ....
    Allora, sono appena arrivato in questo gruppo, quindi la maggior parte dei test dovrei farla su servizi e classi scritte da altri. Il framework non posso dire di conoscerlo bene, ma posso dire che non ci sono interfacce, la Repository non esiste, si lavora direttamente nel Service per quanto riguarda la query, ed il giro non è il "classico" controller-service-repository-db, ma ci sono degli step intermedi che non penso di poter scrivere trattandosi di un framework privato E non posso postare il mio codice per capire almeno se sto facendo qualcosa di sensato
    Ho provato a chiedere informazioni ma pare che nessuno sappia cosa si debba fare, dalle loro risposte.

    Speravo ci fossero delle linee guida da seguire
  • Re: Testare classi con Junit e H2

    WinstonSmith ha scritto:


    Il framework non posso dire di conoscerlo bene, ma posso dire che non ci sono interfacce, la Repository non esiste, si lavora direttamente nel Service per quanto riguarda la query, ed il giro non è il "classico" controller-service-repository-db, ma ci sono degli step intermedi che non penso di poter scrivere trattandosi di un framework privato E non posso postare il mio codice per capire almeno se sto facendo qualcosa di sensato
    Quindi: c'è oppure no un sistema di dependency injection? Detto in generale: all'interno di una classe in uno strato (es. di service) vengono CREATI (con new Xyz(), metodi factory, ecc..) gli oggetti dipendenti?

    In sostanza, se all'interno di un ipotetico BookStoreService viene fatto un new BooksDao(), questa NON è dependency injection. E ciò rende difficile/impossibile fare degli unit-test (ma bisogna vedere dove viene fatto il new e la struttura generale).
  • Re: Testare classi con Junit e H2

    andbin ha scritto:



    Quindi: c'è oppure no un sistema di dependency injection? Detto in generale: all'interno di una classe in uno strato (es. di service) vengono CREATI (con new Xyz(), metodi factory, ecc..) gli oggetti dipendenti?

    In sostanza, se all'interno di un ipotetico BookStoreService viene fatto un new BooksDao(), questa NON è dependency injection. E ciò rende difficile/impossibile fare degli unit-test (ma bisogna vedere dove viene fatto il new e la struttura generale).
    Sì, l'oggetto viene istanziato e non iniettato. In generale, mi pare d'aver capito che pure loro non è che abbiano le idee chiare sulla parte riguardante i test. Mi limito a fare copia ed incolla per ora, poi mi studio il tutto, visto che il tempo è poco. L'unico grosso problema ce l'ho con i metodi void...
    when(serviceMockato.metodo().thanReturn(Cosa?)) 
    se non si aspetta niente come ritorno, come risolvo? se non passo questo punto, non riesco a terminare nulla.
  • Re: Testare classi con Junit e H2

    WinstonSmith ha scritto:


    Sì, l'oggetto viene istanziato e non iniettato.
    E allora può essere un grosso problema (bisogna vedere esattamente la struttura es. del service)

    WinstonSmith ha scritto:


    L'unico grosso problema ce l'ho con i metodi void...
    when(serviceMockato.metodo().thanReturn(Cosa?)) 
    Se stai testando un service (quindi il service è la "unità" di codice sotto test), NON è il service un mock, quindi quello che hai appena scritto servirebbe al massimo per testare qualcosa più sopra ... non il service.
  • Re: Testare classi con Junit e H2

    andbin ha scritto:



    Se stai testando un service (quindi il service è la "unità" di codice sotto test), NON è il service un mock, quindi quello che hai appena scritto servirebbe al massimo per testare qualcosa più sopra ... non il service.
    Sì sì, il questo caso non testo il service, ma una classe con un metodo che grazie ad una istanza del service richiama un determinato metodo. Però se il metodo è void (e lo è sia quello di questa classe che quello del service) non riesco a testare.
  • Re: Testare classi con Junit e H2

    WinstonSmith ha scritto:


    Sì sì, il questo caso non testo il service, ma una classe con un metodo che grazie ad una istanza del service richiama un determinato metodo. Però se il metodo è void (e lo è sia quello di questa classe che quello del service) non riesco a testare.
    Se il service è un mock, allora ok. Ma se il metodo del service che viene chiamato è "void", in realtà NON lo puoi (ovviamente) instrumentare per fargli restituire qualcosa! Quindi quello che hai scritto non ha senso (e non puoi usare when() !).

    Se quel metodo può lanciare una eccezione, puoi fare un caso di test in cui dici che:

    doThrow(new BlaBlaException()).when(serviceMockato).metodo();

    E poi l'obiettivo di questo caso di test è di verificare che la parte "sopra" reagisca correttamente alla eccezione (es. lanci a sua volta una eccezione, restituisca un valore di "fallimento" ecc... NON lo so ovviamente io nel tuo caso).

    Questo puoi fare ....

    Se invece è il caso di test in cui serviceMockato.metodo() deve "filare liscio", ripeto NON puoi fare when() .... è "void" non ti dà nulla. Semplicemente verrà chiamato sul mock. La cosa buona è che il mock "ricorderà" che è stato chiamato e SE VUOI (non è obbligatorio ma è anche questa una asserzione), DOPO che hai chiamato la parte "sopra" (quella che chiama quel serviceMockato.metodo()) puoi verificare se c'è stata l'interazione con quel metodo del mock.

    verify(serviceMockato).metodo();

    Questo verifica che la parte "sopra" (sotto test) abbia chiamato veramente quel metodo del mock. Se così NON fosse, verify lancia una eccezione (specifica di Mockito) per indicare che ti aspettavi che quel metodo venisse invocato e invece ... non lo è (perché evidentemente la parte sotto test è bacata ...).

    Ripeto: questo è quello che puoi fare con i metodi void.
  • Re: Testare classi con Junit e H2

    andbin ha scritto:


    Se il service è un mock, allora ok. Ma se il metodo del service che viene chiamato è "void", in realtà NON lo puoi (ovviamente) instrumentare per fargli restituire qualcosa! Quindi quello che hai scritto non ha senso (e non puoi usare when() !).

    Se quel metodo può lanciare una eccezione, puoi fare un caso di test in cui dici che:

    doThrow(new BlaBlaException()).when(serviceMockato).metodo();

    E poi l'obiettivo di questo caso di test è di verificare che la parte "sopra" reagisca correttamente alla eccezione (es. lanci a sua volta una eccezione, restituisca un valore di "fallimento" ecc... NON lo so ovviamente io nel tuo caso).

    Questo puoi fare ....

    Se invece è il caso di test in cui serviceMockato.metodo() deve "filare liscio", ripeto NON puoi fare when() .... è "void" non ti dà nulla. Semplicemente verrà chiamato sul mock. La cosa buona è che il mock "ricorderà" che è stato chiamato e SE VUOI (non è obbligatorio ma è anche questa una asserzione), DOPO che hai chiamato la parte "sopra" (quella che chiama quel serviceMockato.metodo()) puoi verificare se c'è stata l'interazione con quel metodo del mock.

    verify(serviceMockato).metodo();

    Questo verifica che la parte "sopra" (sotto test) abbia chiamato veramente quel metodo del mock. Se così NON fosse, verify lancia una eccezione (specifica di Mockito) per indicare che ti aspettavi che quel metodo venisse invocato e invece ... non lo è (perché evidentemente la parte sotto test è bacata ...).

    Ripeto: questo è quello che puoi fare con i metodi void.
    Grazie mille, ho utilizzato questa soluzione "verify(serviceMockato).metodo();", devo solo vedere cosa mi dicono. Mentre avevo letto della soluzione per verificare l'eccezione, ma non andava bene poiché il test deve andare a buon fine.
    Resto comunque poco convinto del modo in cui sto scrivendo queste classi di test... insomma, per quale motivo dovrebbe lanciare un'eccezione, non andrà sempre a buon fine? Immagino serva altro, non solo queste due righe di codice...
     @Test
        public void executeTest() throws Exception { 
        	serviceMock.delete(null);
        	 verify(serviceMock).delet(null);  
        }
  • Re: Testare classi con Junit e H2

    WinstonSmith ha scritto:


    Mentre avevo letto della soluzione per verificare l'eccezione, ma non andava bene poiché il test deve andare a buon fine.
    Quello della eccezione è un altro caso. Devi verificare (possibilmente) tutti gli scenari .... (questo è il bello degli unit test ... ragionare su tutti gli scenari che hanno senso )

    WinstonSmith ha scritto:


    insomma, per quale motivo dovrebbe lanciare un'eccezione, non andrà sempre a buon fine?
    Ti ripeto che è un ALTRO caso di test, lo scenario di eccezione.

    WinstonSmith ha scritto:


    Immagino serva altro, non solo queste due righe di codice...
     @Test
        public void executeTest() throws Exception { 
        	serviceMock.delete(null);
        	 verify(serviceMock).delet(null);  
        }
    Ma questo non ha senso. I metodi dell'oggetto "mock" NON li devi invocare tu nello unit test! Li deve invocare la parte "di sopra" che è la unità sotto test.
  • Re: Testare classi con Junit e H2

    andbin ha scritto:


    Ma questo non ha senso. I metodi dell'oggetto "mock" NON li devi invocare tu nello unit test! Li deve invocare la parte "di sopra" che è la unità sotto test.
    Dove? Ed a questo punto, eliminando la riga "service.delete(null);" , il test non fallirà sempre?

    Provo a spiegarmi, per capire cosa devo fare.
    Mentre con un servizio che prende dati dal db, quando sono in questa classe mi basta fare when(serviziomockato.metodo()).thenReturn(qualcosa);
    se è void abbiamo detto che posso tener traccia di una eventuale utilizzo del serviceMock che chiama quel metodo, ma in che modo?
  • Re: Testare classi con Junit e H2

    WinstonSmith ha scritto:


    Dove? Ed a questo punto, eliminando la riga "service.delete(null);" , il test non fallirà sempre?
    Il service è un mock, no? Allora ci sarà un'altra classe (quella SOTTO test, da testare) che ci si aspetta debba invocare il/i metodi del mock. Nel metodo di test dovrai invocare tu un metodo della classe sotto test e ... vedere come si comporta.
  • Re: Testare classi con Junit e H2

    andbin ha scritto:



    Il service è un mock, no? Allora ci sarà un'altra classe (quella SOTTO test, da testare) che ci si aspetta debba invocare il/i metodi del mock. Nel metodo di test dovrai invocare tu un metodo della classe sotto test e ... vedere come si comporta.
    Sarà che è un argomento nuovo, la fretta, ma non riesco a capirlo.
    Per come avevo inteso io, questa classe, la classe controller, la Service erano tra loro separate, si testava la singola classe, che non in contatto con le altre. Tnt'è che la classe con il servicemock che invece preleva dati dal db, "funziona"... when(serviceMock.getAll()).thenReturn(listacreataDueRighePrima)
  • Re: Testare classi con Junit e H2

    WinstonSmith ha scritto:


    Sarà che è un argomento nuovo, la fretta, ma non riesco a capirlo.
    Per come avevo inteso io, questa classe, la classe controller, la Service erano tra loro separate, si testava la singola classe, che non in contatto con le altre. Tnt'è che la classe con il servicemock che invece preleva dati dal db, "funziona"... when(serviceMock.getAll()).thenReturn(listacreataDueRighePrima)
    Ascolta, allora: il tuo scenario, immagino, sarà qualcosa del tipo (tipico, comunque, in generale):

    Controller ----> Service ----> Dao

    Vuoi testare il Controller? Bene. Il Controller ha di certo un riferimento al Service e invocherà i suoi metodi in qualche modo in base a qualche logica.
    Nello unit test del Controller NON devi avere il Service "reale". Devi averne uno fittizio, un mock appunto.
    Quindi nella classe di test dovrai creare un "mock" per il Service. E attenzione, devi fare in modo che il Controller usi QUESTO mock e non il Service "reale"! Pertanto devi o impedire che il Controller si crei per conto suo il Service reale oppure devi "rimpiazzare" (magari con un apposito setter se c'è o se è possibile metterlo) l'oggetto Service in modo da fargli usare il mock.

    Dopo questa fase di setup generale dell'oggetto sotto-test, ciascun metodo @Test avrà tipicamente 3 fasi:

    1) imposta il/i mock usando when() / doThrow() ecc... secondo i casi e quale metodo del mock.
    2) invoca il metodo dell'oggetto sotto test (nel tuo caso del Controller).
    3) verifica le aspettative, in termini sia di "assert" (es. sul valore di ritorno, se c'è) oppure di interazione con i mock (i verify() ).
  • Re: Testare classi con Junit e H2

    Grazie ancora per tutte le tue dritte e spiegazioni. Basandomi su queste sono riuscito a mettere giù qualcosa, la copertura non è delle migliori, ed uno dei servizi da testare non copre proprio, ma almeno tra te, tutorial e pdf sono riuscito a fare qualcosa.
Devi accedere o registrarti per scrivere nel forum
18 risposte