Modern unit és integrációs tesztelés HOUG Orákulum - 2016. április
Viczián István Java fejlesztő - IP Systems @vicziani at Twitter http://jtechlog.hu
1 / 26
Miről lesz szó? Automatizált tesztelés fontossága Unit és integrációs tesztek Élő dokumentáció Példaprojekt: https://github.com/vicziani/spring-training
2 / 26
Gyors
3 / 26
Tesztelendő unit Coord public static Coord parse(String s) { String[] items = s.split(","); if (items.length != 2) { throw new IllegalArgumentException( String.format("Invalid coordinate: %s", s)); } double lat = Double.parseDouble(items[0].trim()); double lon = Double.parseDouble(items[1].trim()); return new Coord(lat, lon); }
4 / 26
Unit teszt CoordTest @Test public void shouldParse() { String position = "47.4811281,18.9902207"; Coord coord = Coord.parse(position); assertThat(coord.getLat(), is(47.4811281)); assertThat(coord.getLon(), is(18.9902207)); }
5 / 26
Unit teszt Hamcresttel CoordTest @Test public void shouldParseAssertWithMatcher() { String position = "47.4811281,18.9902207"; assertThat(Coord.parse(position), isCoord(is(47.4811281), is(18.9902207))); }
6 / 26
Unit teszt rule-lal CoordTest @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void shoudThrowIllegalArgumentException() { String position = "abc"; thrown.expect(IllegalArgumentException.class); thrown.expectMessage(is("Invalid coordinate: abc")); Coord.parse(position); }
7 / 26
Paraméterezett unit teszt CoordParametrizedTest @Parameterized.Parameters(name = "{index}: Coord.parse for {0} throws {1}") public static Collection
data() { return Arrays.asList(new Object[][] { { null, NullPointerException.class }, { "", IllegalArgumentException.class }, { "1", IllegalArgumentException.class }, { "1,2,3", IllegalArgumentException.class }, { "1,a", NumberFormatException.class }, { "a,1", NumberFormatException.class }, { ",1", NumberFormatException.class }, { ",,", IllegalArgumentException.class } }); }
8 / 26
Biztosítja a struktúrát
9 / 26
Nem unit tesztelhető public Page listLocations(Pageable pageable) { return new LocationRepositoryImpl() .findAll(pageable).map(new LocationDtoConverter()); }
10 / 26
Tesztelhető LocationService public class LocationService { private LocationRepository locationRepository; @Autowired public LocationService(LocationRepository locationRepository) { this.locationRepository = locationRepository; } public Page listLocations(Pageable pageable) { return locationRepository.findAll(pageable) .map(new LocationDtoConverter()); } }
11 / 26
Teszt Mockitoval LocationServiceTest @RunWith(MockitoJUnitRunner.class) public class LocationServiceTest { @Mock LocationRepository locationRepository; @InjectMocks LocationService locationService; @Test public void shouldCallSave() { locationService.createLocation( new CreateLocationDto("Budapest", "47.4811281,18.9902207"));
}
}
verify(locationRepository).save( (Location) argThat(hasProperty("name", is("Budapest"))));
12 / 26
Biztonságos
13 / 26
Refaktoring Olvashatóság Komplexitás csökkentése Kód befogadás Teszt - refaktoring - teszt
14 / 26
Unit vagy integrációs teszt?
15 / 26
Integrációs teszt Teszt piramis? Minden teszteset előtt adatbázis törlés Alapadatok feltöltése az alkalmazáson keresztül
16 / 26
Integrációs teszt controlleren LocationControllerIntegrationTest @Test public void afterSaveShouldQuery() { CreateLocationDto createLocationDto = new CreateLocationDto("Budapest", "47.4811281,18.9902207"); locationController.postSaveLocation(createLocationDto, new BeanPropertyBindingResult(createLocationDto, "createLocationDto"), new PageRequest(0, 50), new RedirectAttributesModelMap());
}
ModelAndView modelAndView = locationController .getListLocations(new PageRequest(0, 50)); List locations = ((Page) modelAndView.getModelMap() .get("locations")).getContent(); assertThat(locations, hasSize(1)); assertThat(locations, hasItem(locationWithName(is("Budapest")) .withLat(is(47.4811281)).withLon(is(18.9902207))));
17 / 26
Integrációs teszt Spring Mock MVC-vel LocationControllerMockMvcIntegrationTest @Test public void afterSaveShouldQuery() throws Exception { mockMvc.perform(post("/") .param("name", "Budapest") .param("position", "47.4811281,18.9902207")) .andExpect(status().is3xxRedirection());
}
mockMvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(model().attribute("locations", hasItem(locationWithName(is("Budapest")) .withLat(is(47.4811281)).withLon(is(18.9902207)))));
18 / 26
Integrációs teszt, assert a renderelt view-n LocationControllerMockMvcIntegrationTest mockMvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(content().string(containsString("Budapest")));
19 / 26
Integrációs teszt rest controlleren LocationRestControllerIntegrationTest @Test public void afterSaveShouldQuery() { locationRestController.postSaveLocation( new CreateLocationDto("Budapest", "47.4811281,18.9902207"));
}
List locations = locationRestController.getListLocations( new PageRequest(0, 50)).getContent(); assertThat(locations, hasSize(1)); assertThat(locations, hasItem(locationWithName(is("Budapest")) .withLat(is(47.4811281)).withLon(is(18.9902207))));
20 / 26
Integrációs teszt Mock Mvc-vel LocationRestControllerMockMvcIntegrationTest @Test public void afterSaveShouldQuery() throws Exception { mockMvc.perform(post("/api/locations") .param("name", "Budapest") .param("position", "47.4811281,18.9902207")) .andExpect(status().isOk());
}
mockMvc.perform(get("/api/locations")) .andExpect(status().isOk()) .andExpect(jsonPath("$.content[0].name", is("Budapest"))) .andExpect(jsonPath("$.content[0].lat", is(47.4811281))) .andExpect(jsonPath("$.content[0].lon", is(18.9902207)));
21 / 26
Integrációs tesztelés driverrel LocationRestControllerDriverIntegrationTest @Test public void afterSaveShouldQuery() { locationDriver .newLocation() .withName("Budapest") .withPosition("47.4811281,18.9902207") .create()
}
.listLocations() .locationsSize(1) .hasLocation(locationWithName(is("Budapest")) .withLat(is(47.4811281)).withLon(is(18.9902207)));
22 / 26
Élő dokumentáció
23 / 26
Concordion HTML dokumentáció, mögötte integrációs tesztek Hiba esetén színezés Szétválik a dokumentációs és kód Szétválik a funkció leírása és a példa Nem kötött formátum Dokumentáció generálható
24 / 26
Concordion HTML Location.html Felveszek egy új helyet <span concordion:set="#name">Budapest névvel <span concordion:set="#position">47.4811281, 18.9902207 koordinátával.
<span concordion:execute="queryByName(#name)"/> Ha lekérdezem a megadott névvel, a szélességi fok <span concordion:assertEquals="getLat()">47.4811281, és a hosszúsági fok <span concordion:assertEquals="getLon()">18.9902207 lesz.
25 / 26
Concordion Java teszt LocationTest public void saveLocation(String name, String position) { locationDriver .newLocation() .withName(name) .withPosition(position) .create(); } public void queryByName(String name) { locationDto = locationDriver .listLocations() .findByName(name); } public double getLat() { return locationDto.lat; } public double getLon() { return locationDto.lon; }