Integratsioonitestid
Sissejuhatus
Mock'imise peatükis tutvusime, kuidas üks test saab sujuvamalt testida mingit funktsionaalsust isolatsioonis, asendades mingid kindlad sõltuvused immitatsioonidega.
See toimib hästi ühe klassi kaupa kontrollimiseks, kuid ei näita, kas klassid tegelikult ka koos töötavad.
Integratsioonitest kontrollib, et mitu reaalset komponenti töötaksid koos korrektselt. Immiteerimise asemel kasutatakse reaalseid implementatsioone ning testitakse ühes suures ahelas.
Üksustestid vs Integratsioonitestid
Vaatleme rakendust, mis kasutab kolmekihilist arhitektuuri: Controller kutsub Service-t, mis omakorda kutsub Repository-t.
Üksustest BookService-ile immiteerib BookRepository teostust ja testib ainult teenuse loogikat:
Test -> BookService -> mock(BookRepository)
Integratsioonitest seob Controller-i ja Service-i kokku, kasutades immiteeritud Repository-t, ning testib kogu päringutöötluse ahelat kahe reaalse kihi ulatuses:
Test -> LibraryController -> BookService -> mock(BookRepository)
Antud juhul LibraryController on reaalne objekt, mis töötleb päringuid ning delegeerib neid BookService-le, mis rakendab äriloogikat ja kutsub immiteeritud andmehoidlat.
See test kontrollib, et kontroller töötleb päringuid õigesti ning kutsub teenusest vajalike meetodeid ning tagastab õige tulemuse.
Üksustestid omaette sellist kooslust ei ole suutelised kontrollima.
Miks on integratsioonitestid olulised
Mock'idega üksustestid kontrollivad, et iga klass teeb oma tööd eeldusel, et sõltuvused käituvad ootuspäraselt. Kuid mockid põhinevad eeldustel, ja eeldused võivad olla valed:
- Kontroller võib päringu õigesti töödelda, kuid kutsuda vale teenuse meetodit.
- Teenus võib eeldada, et andmehoidla tagastab
List-i, kuid kontroller edastab tulemuse ilma.toString()-i kutsumata. - Parameetrite nimed võidakse töödelda vales järjekorras.
Integratsioonitestid leiavad sellised sidumisvead (wiring bugs) - probleemid, mis ilmnevad alles siis, kui päris komponendid omavahel suhtlevad.
Integratsioonitestide loomine
Integratsioonitest kontroller-teenuse ahelale näeb välja sarnane üksustestiga, kuid sisaldab rohkem päris objekte ja nende omavahelisi ühendusi:
@Test
void testHandleRequest_getBook_returnsCorrectBook() {
// Arrange — wire real Controller and Service, mock only the Repository
BookRepository repository = mock(BookRepository.class);
BookService service = new BookService(repository);
LibraryController controller = new LibraryController(service);
Book expectedBook = new Book("Clean Code", "Robert Martin", 2008, new BigDecimal("35"));
when(repository.existsByTitleAndAuthor("Clean Code", "Robert Martin")).thenReturn(true);
when(repository.getByTitleAndAuthor("Clean Code", "Robert Martin")).thenReturn(expectedBook);
// Act — send a request string through the controller
String result = controller.handleRequest("/get_book?title=Clean Code&author=Robert Martin");
// Assert — the full chain produced the correct output
assertEquals(expectedBook.toString(), result);
}
See test katab kogu teekonna: kontroller töötleb /get_book?title=Clean Code&author=Robert Martin päringu, eraldab parameetrid, kutsub välja service.getBook("Clean Code", "Robert Martin") meetodi, mis kontrollib olemasolu andmehoidla kaudu ja tagastab raamatu.
Kui selles ahelas on mõni lüli katki (vale parameetrite töötlemine, vale meetodikutse, puuduv null-kontroll), siis test ebaõnnestub.
Mida immiteerida integratsioonitestides
Integratsioonitestid ei tähenda üldse, et mock'e kasutama ei peaks. Eesmärk on kasutada päris teostusi nende komponentide jaoks, mida soovid koos testida, ning immiteerida need komponendid, mis jäävad testi skoobist välja või mida on ebapraktiline kasutada.
Kolmekihilises arhitektuuris:
| Mis on testimise all | Mida immiteerida |
|---|---|
| Controller + Service | Repository (ei eksisteeri antud skoobis) |
| Service + Repository | Mitte midagi (kui sul on in-memory andmehoidla olemas) |
| Controller + Service + Repository | Välised API-d, email teenused, jne. |
Immiteerimise piir sõltub sellest, mida soovid kontrollida. Üldiselt immiteeri seda, mis on testi skoobist väljas, ja kasuta päris objekte kõige jaoks, mis on selle sees.
Testiklasside strukturiseerimine
Tüüpiliselt luuakse eraldi klassid üksus- ja integratsioonitestide jaoks:
test/
├── BookServiceTest.java // Unit tests — Service with mocked Repository
├── LibraryControllerTest.java // Unit tests — Controller with mocked Service
├── LibraryControllerIntegrationTest.java // Integration — Controller + Service + mocked Repository