2012-09-17 11 views
6

W naszym ostatnim projekcie Sonar narzekał na słaby zasięg testu. Zauważyliśmy, że domyślnie nie uwzględnia on testów integracyjnych. Oprócz tego, że możesz skonfigurować Sonar, więc rozważy je (wtyczka JaCoCo), omawialiśmy pytanie w naszym zespole, jeśli naprawdę istnieje potrzeba napisania testów jednostkowych, gdy pokryjesz całą warstwę usług i bazy danych testami integracyjnymi tak czy inaczej.Czy powinienem napisać testy jednostkowe dla operacji CRUD, gdy mam już testy integracji?

Co mam na myśli w przypadku testów integracyjnych, że wszystkie nasze testy są przeprowadzane na dedykowanej instancji Oracle tego samego typu, którego używamy w produkcji. Nie kpimy z niczego. Jeśli usługa zależy od innej usługi, korzystamy z prawdziwej usługi. Dane, których potrzebujemy przed uruchomieniem testu, tworzymy za pomocą klas fabrycznych, które korzystają z naszych usług/repozytoriów (DAO).

Z mojego punktu widzenia pisanie testów integracyjnych prostych operacji CRUD, szczególnie przy użyciu frameworków takich jak Spring Data/Hibernate, nie jest dużym wysiłkiem. Czasem jest jeszcze łatwiej, bo nie myślisz o tym, co i jak kpić.

Dlaczego więc powinienem pisać testy jednostkowe dla moich operacji CRUD, które są mniej wiarygodne, jak testy integracji, które mogę napisać?

Jedyny punkt, który widzę, to to, że testy integracyjne będą trwały dłużej, im większy będzie projekt. Więc nie chcesz ich wszystkich uruchamiać przed odprawą. Ale nie jestem pewien, czy to jest takie złe, jeśli masz środowisko CI z Jenkinsem/Hudsonem, które wykona zadanie.

Tak więc, wszelkie opinie i sugestie są wysoko cenione!

Odpowiedz

10

Jeśli większość twoich usług po prostu przechodzi do twoich daos, a twoi daos robią niewiele, ale wywołują metody na Spring HibernateTemplate lub JdbcTemplate to masz rację, że testy jednostkowe nie dowodzą niczego, co już udowodniłeś w testach integracyjnych. Jednak posiadanie testów jednostkowych jest cenne ze wszystkich zwykłych powodów.

Ponieważ testy jednostkowe przetestować tylko pojedyncze zajęcia, prowadzone w pamięci bez dysku lub sieci dostępowej i nigdy naprawdę przetestować wiele klas pracujących razem, normalnie iść tak: testy jednostkowe

  • serwisowe mock DAOs.
  • Testy jednostkowe typu Dao pozorują sterownik bazy danych (lub szablon sprężyny) lub korzystają z wbudowanej bazy danych (bardzo łatwe na wiosnę 3).

Do testów jednostkowych usługa, która właśnie przechodzi do dao, można drwić tak:

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

Ale jest to naprawdę dobry pomysł? Te twierdzenia Mockito potwierdzają, że metoda dao została wywołana, a nie, że zrobiła to, co oczekiwano! Otrzymasz numery ubezpieczenia, ale mniej lub bardziej wiążą twoje testy do wdrożenia dao. Oooo.

Ten przykład zakłada, że ​​usługa nie miała prawdziwej logiki biznesowej. Zwykle usługi będą miały logikę biznesową oprócz wywołań dao, a ty na pewno musisz je przetestować.

Teraz, dla testów jednostkowych, lubię korzystać z wbudowanej bazy danych.

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

Nadal nazywam to testem jednostkowym, chociaż większość ludzi może nazwać to testem integracyjnym.Wbudowana baza danych znajduje się w pamięci i jest rozwijana i usuwana po uruchomieniu testów. Jest to jednak zależne od tego, czy osadzona baza danych wygląda tak samo, jak baza danych produkcji. Czy tak właśnie będzie? Jeśli nie, to cała ta praca była całkiem bezużyteczna. Jeśli tak, to, jak mówisz, testy te robią coś innego niż testy integracyjne. Ale mogę je uruchomić na żądanie z mvn test i mam zaufanie do refactor.

Dlatego piszę te testy jednostkowe i spełniają moje cele dotyczące zasięgu. Podczas pisania testów integracji twierdzę, że żądanie HTTP zwraca oczekiwaną odpowiedź HTTP. Tak, to podciąga testy jednostkowe, ale hej, kiedy ćwiczysz TDD, masz testy jednostkowe napisane przed twoją rzeczywistą implementacją dao.

Jeśli piszesz testy jednostkowe po dao, to oczywiście nie są one zabawne w pisaniu. Literatura TDD jest pełna ostrzeżeń na temat tego, jak pisanie testów po twoim kodzie wydaje się działać i nikt nie chce tego robić.

TL; DR: Twoje testy integracyjne będą obejmować testy jednostkowe iw tym sensie testy jednostkowe nie dodają rzeczywistej wartości testowej. Jednak, gdy masz pakiet testów jednostkowych o dużym zasięgu, masz pewność, że refactor. Ale oczywiście, jeśli dao trywialnie nazywa szablon dostępu do danych Spring, to może nie być refaktoryzacji. Ale nigdy nie wiesz. I na koniec, jeśli testy jednostkowe zostaną napisane najpierw w stylu TDD, i tak je otrzymasz.

+0

Ray, bardzo dziękuję za szczegółowe wyjaśnienia i przykłady! Sądzę, że najpierw będziemy trzymać się podejścia do pisania testów integracyjnych. Posunąłbym się nawet do stwierdzenia, że ​​testy jednostkowe usług CRUD aplikacji internetowej są niezłe, ale tak naprawdę liczy się test integracyjny. i dlaczego nie robić tego w trybie TDD, ale zamiast testów jednostkowych pisać testy integracyjne przed wdrożeniem usługi? – fischermatte

+0

To będzie dobrze; policja testów jednostkowych nie zapuka do twoich drzwi. Prawdą jest, że testy integracyjne utworzą testy jednostkowe.Osobiście cieszę się, że mam tam testy jednostkowe (pewność refaktoryzacji, poczucie kompletności i tak dalej), ale to tylko ja. Jeśli jesteś zadowolony z uzyskania zasięgu dzięki testom integracyjnym, a nie testom jednostkowym, a testy integracyjne są szybkimi testami programistycznymi generowanymi przez programistów, w przeciwieństwie do generowanych przez QA testów funkcjonalno-integracyjnych, testów akceptacyjnych i produkcyjno-produkcyjnych, a następnie cool. :) –

1

Naprawdę potrzebujesz tylko przetestować każdą warstwę oddzielnie, jeśli planujesz wystawić warstwy na inne komponenty z projektu. W przypadku aplikacji sieciowej jedynym sposobem wywołania warstwy repozytorium jest warstwa usług, a jedynym sposobem wywołania warstwy usługi jest warstwa kontrolera. Testowanie może się rozpocząć i zakończyć na warstwie kontrolera. W przypadku zadań działających w tle są one wywoływane w warstwie usługi, dlatego należy je przetestować tutaj.

Testowanie za pomocą prawdziwej bazy danych jest dość szybkie w dzisiejszych czasach, więc nie spowalnia testów zbyt mocno, jeśli dobrze zaprojektujesz konfigurację/zrywanie. Jeśli jednak istnieją inne zależności, które mogą być powolne lub problematyczne, powinny one być kpiąco.

Takie podejście daje:

  • dobre pokrycie
  • realistics testuje
  • minimalnej ilości wysiłku
  • minimalnej ilości refectoring wysiłku

Jednak badania warstw w izolacji pozwala twojemu zespołowi pracować bardziej równolegle, więc jeden programista może zrobić repozytorium, a inny może wykonywać usługi r jeden element funkcjonalności i produkować niezależnie sprawdzoną pracę.

Zawsze będzie podwójne pokrycie, gdy selen/testy funkcjonalne są włączone, ponieważ nie można polegać na nich samych, ponieważ są one zbyt wolne, aby uruchomić. Jednak testy funkcjonalne nie muszą koniecznie obejmować całego kodu, sama podstawowa funkcjonalność może być wystarczająca, dopóki kod został objęty testami jednostkowymi/integracyjnymi.