Se la maggior parte dei tuoi servizi passa semplicemente ai tuoi daos, e i tuoi dao fanno poco, ma invocano i metodi su Spring HibernateTemplate
o JdbcTemplate
, allora sei corretto che i test di unità non provano in realtà nulla che i tuoi test di integrazione già dimostrino. Tuttavia, i test unitari in atto sono preziosi per tutti i soliti motivi.
Dal test di unità di prova solo singole classi, eseguire in memoria senza disco o rete di accesso, e mai realmente testare più classi che lavorano insieme, che normalmente vanno in questo modo: i test di unità
- servizio deridere il Tao.
- I test dell'unità Dao simulano il driver del database (o il modello di primavera) o utilizzano un database incorporato (super facile in Spring 3).
Per unit test del servizio che passa solo attraverso il dao, si può deridere in questo modo:
@Before
public void setUp() {
service = new EventServiceImpl();
dao = mock(EventDao.class);
service.EventDao = dao;
}
@Test
public void creationDelegatesToDao() {
service.createEvent(sampleEvent);
verify(dao).createEvent(sampleEvent);
}
@Test(expected=EventExistsException.class)
public void creationPropagatesExistExceptions() {
doThrow(new EventExistsException()).when(dao).createEvent(sampleEvent);
service.createEvent(sampleEvent);
}
@Test
public void updatesDelegateToDao() {
service.updateEvent(sampleEvent);
verify(dao).updateEvent(sampleEvent);
}
@Test
public void findingDelgatesToDao() {
when(dao.findEventById(7)).thenReturn(sampleEvent);
assertThat(service.findEventById(7), equalTo(sampleEvent));
service.findEvents("Alice", 1, 5);
verify(dao).findEventsByName("Alice", 1, 5);
service.findEvents(null, 10, 50);
verify(dao).findAllEvents(10, 50);
}
@Test
public void deletionDelegatesToDao() {
service.deleteEvent(sampleEvent);
verify(dao).deleteEvent(sampleEvent);
}
Ma è davvero una buona idea? Queste asserzioni di Mockito affermano che è stato chiamato un metodo dao, non che ha fatto ciò che ci si aspettava! Otterrete i vostri numeri di copertura ma vincerete più o meno i vostri test a un'implementazione del dao. Ahia.
Ora questo esempio presuppone che il servizio non abbia una vera logica aziendale. Normalmente i servizi avranno una logica di business in aggiunta alle chiamate dao, e sicuramente dovrai testarli.
Ora, per i test di unità daos, mi piace utilizzare un database incorporato.
private EmbeddedDatabase database;
private EventDaoJdbcImpl eventDao = new EventDaoJdbcImpl();
@Before
public void setUp() {
database = new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("schema.sql")
.addScript("init.sql")
.build();
eventDao.jdbcTemplate = new JdbcTemplate(database);
}
@Test
public void creatingIncrementsSize() {
Event e = new Event(9, "Company Softball Game");
int initialCount = eventDao.findNumberOfEvents();
eventDao.createEvent(e);
assertThat(eventDao.findNumberOfEvents(), is(initialCount + 1));
}
@Test
public void deletingDecrementsSize() {
Event e = new Event(1, "Poker Night");
int initialCount = eventDao.findNumberOfEvents();
eventDao.deleteEvent(e);
assertThat(eventDao.findNumberOfEvents(), is(initialCount - 1));
}
@Test
public void createdEventCanBeFound() {
eventDao.createEvent(new Event(9, "Company Softball Game"));
Event e = eventDao.findEventById(9);
assertThat(e.getId(), is(9));
assertThat(e.getName(), is("Company Softball Game"));
}
@Test
public void updatesToCreatedEventCanBeRead() {
eventDao.createEvent(new Event(9, "Company Softball Game"));
Event e = eventDao.findEventById(9);
e.setName("Cricket Game");
eventDao.updateEvent(e);
e = eventDao.findEventById(9);
assertThat(e.getId(), is(9));
assertThat(e.getName(), is("Cricket Game"));
}
@Test(expected=EventExistsException.class)
public void creatingDuplicateEventThrowsException() {
eventDao.createEvent(new Event(1, "Id1WasAlreadyUsed"));
}
@Test(expected=NoSuchEventException.class)
public void updatingNonExistentEventThrowsException() {
eventDao.updateEvent(new Event(1000, "Unknown"));
}
@Test(expected=NoSuchEventException.class)
public void deletingNonExistentEventThrowsException() {
eventDao.deleteEvent(new Event(1000, "Unknown"));
}
@Test(expected=NoSuchEventException.class)
public void findingNonExistentEventThrowsException() {
eventDao.findEventById(1000);
}
@Test
public void countOfInitialDataSetIsAsExpected() {
assertThat(eventDao.findNumberOfEvents(), is(8));
}
Lo chiamo ancora un test di unità anche se molte persone potrebbero chiamarlo un test di integrazione.Il database incorporato risiede in memoria e viene richiamato e rimosso quando vengono eseguiti i test. Ma questo si basa sul fatto che il database incorporato ha lo stesso aspetto del database di produzione. Sarà così? Altrimenti, tutto quel lavoro sarebbe stato piuttosto inutile. Se è così, allora, come dici tu, questi test stanno facendo qualcosa di diverso rispetto ai test di integrazione. Ma posso eseguirli su richiesta con mvn test
e ho fiducia nel refactoring.
Pertanto, scrivo comunque questi test unitari e raggiungo i miei obiettivi di copertura. Quando scrivo test di integrazione, asserisco che una richiesta HTTP restituisce la risposta HTTP prevista. Sì, si susseguono i test unitari, ma ciao, quando pratichi TDD hai comunque quei test unitari scritti prima della tua effettiva implementazione dao.
Se si scrivono unit test dopo il dao, ovviamente non sono divertenti da scrivere. La letteratura TDD è piena di avvertimenti su come scrivere test dopo che il tuo codice sembra funzionare e nessuno vuole farlo.
TL; DR: i test di integrazione precedono i test dell'unità e in tal senso i test di unità non aggiungono valore di test reale. Tuttavia, quando si dispone di una suite di test unitaria ad alta copertura, si ha fiducia nel refactoring. Ma ovviamente se il dao sta banalmente chiamando il modello di accesso ai dati di Spring, allora potresti non essere il refactoring. Ma non lo sai mai. E, infine, se i test delle unità vengono scritti prima in stile TDD, li avrete comunque.
Ray, grazie mille le tue spiegazioni dettagliate ed esempi! Credo che attenersimo all'approccio per la scrittura dei test di integrazione. Andrei anche così lontano per dire che i test unitari per i servizi CRUD di un'applicazione web sono carini, ma ciò che conta davvero sono i test di integrazione. e perché non farlo nel modo TDD, ma scrivere test di integrazione invece di test unitari prima di implementare il servizio? – fischermatte
Questo andrà bene; la polizia del test unitario non busserà alla tua porta. È vero che i test di integrazione copriranno i test unitari.Personalmente mi godo la sensazione di avere l'unità testata lì (fiducia nel refactoring, una sensazione di completezza e tutto il resto) ma sono solo io. Se sei felice di ottenere la tua copertura attraverso test di integrazione rispetto ai test unitari, e i tuoi test di integrazione sono rapidi test di integrazione generati dagli sviluppatori in opposizione ai test di produzione-integrazione-accettazione-produzione generati dal QA, quindi interessanti. :) –