Upgrade to Pro — share decks privately, control downloads, hide ads and more …

They say testing triangle but you are doing microservices

They say testing triangle but you are doing microservices

They say testing triangle but you are doing microservices. Now what?

Unit / Integration / GUI - an obvious association when anybody mentions a test triangle. Every ISTQB student can drill down these elements when woken up in the middle of the night. The thing is: is this decomposition still relevant? Does it even work when you run a highly distributed, microservice environment? Does this make any sense in a microservices world?.

During this session we will walk through different test goals, a different hypothesis we want to assert during testing, evaluate tools that can support us and see these tools in action: on a "real" java based microservices. So expect some critical thinking and a walk-through a set of tools and methods on how to use unit and integration tests effectively. But first and foremost, we will look beyond that and look when to initialise application container, how to approach validation of the service's contracts and finally is it worth to mimic a part of production environment with test containers.

Jakub Marchwicki

December 15, 2021
Tweet

More Decks by Jakub Marchwicki

Other Decks in Technology

Transcript

  1. Jakub Marchwicki <@kubem> they say testing triangle but you are

    doing microservices now what? Jakub Marchwicki <@kubem>
  2. Jakub Marchwicki <@kubem> @JsonTest public class EventMappingTest { @Autowired ObjectMapper

    mapper; @Test public void should_be_deserialized_when_unknown_properties () throws IOException { var event = mapper.readValue("{\n" + "\"playerId\": \"f9b25263-8934-4c14-ac12-c21fe74c8b88 \",\n" + "\"someUnknownField \": \"ST1568706084974 \"\n" + "}", SampleBean.class); assertThat(event.getPlayerId(), is("f9b25263-8934-4c14-ac12-c21fe74c8b88" )); } @Test public void should_serialize_the_dates () throws IOException { ZonedDateTime time = ZonedDateTime.of(2019, 9, 11, 13, 23, 00, 00, ZoneId.of("Europe/Warsaw" )); SampleBean bean = new SampleBean(randomUUID().toString(), time); var serializedEvent = mapper.writeValueAsString( bean); assertThat(serializedEvent , containsString("playerId")); assertThat(serializedEvent , containsString("timestamp")); assertThat(serializedEvent , containsString("2019-09-11T13:23:00+02:00" )); } } @Value class SampleBean { String playerId; String brandId; ZonedDateTime timestamp; }
  3. Jakub Marchwicki <@kubem> public class EventMappingTest { ObjectMapper mapper =

    new ObjectMapper() ; @Test public void should_be_deserialized_when_unknown_properties () throws IOException { var event = mapper.readValue("{\n" + "\"player_id\": \"f9b25263-8934-4c14-ac12-c21fe74c8b88 \",\n" + "\"some_unknown_field \": \"ST1568706084974 \"\n" + "}", SampleBean.class); assertThat(event.getPlayerId(), is("f9b25263-8934-4c14-ac12-c21fe74c8b88" )); } @Test public void should_serialize_the_dates () throws IOException { ZonedDateTime time = ZonedDateTime.of(2019, 9, 11, 13, 23, 00, 00, ZoneId.of("Europe/Warsaw" )); SampleBean bean = new SampleBean(randomUUID().toString(), time); var serializedEvent = mapper.writeValueAsString( bean); assertThat(serializedEvent , containsString("playerId")); assertThat(serializedEvent , containsString("timestamp")); assertThat(serializedEvent , containsString("2019-09-11T13:23:00+02:00" )); } } @Value class SampleBean { String playerId; String brandId; ZonedDateTime timestamp; } This will fail
  4. Jakub Marchwicki <@kubem> basic integration testing Is the framework used

    correctly? Is configuration read properly? Are objects serialized and deserialized? Logic works with dependencies abstracted
  5. Jakub Marchwicki <@kubem> when we wait for the tests to

    finish 0.1 seconds - the system is reacting instantaneously 1 second - limit for the user's flow of thought to stay uninterrupted, even though the user will notice the delay 10 seconds - limit for keeping the user's attention focused on the process Response Times: The 3 Important Limits [Miller 1968; Card et al. 1991]
  6. Jakub Marchwicki <@kubem> @JsonTest @WireMockTest class HazelcastCloudClientRetryTest { @Autowired ObjectMapper

    objectMapper; @Test void it_should_get_clusters() { //given stubFor(get(urlPathEqualTo("/cluster/all")) .willReturn( aResponse().withBodyFile("api/get_all_clusters.json") .withHeader(CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).withStatus(200)) ); //when Clusters clusters = this.hazelcastCloudClient.getClusters(); //then assertEquals(3, clusters.getNumberOfElements()); } }
  7. Jakub Marchwicki <@kubem> @JsonTest class VendorClientTest { @Autowired private ObjectMapper

    objectMapper ; private Client client; @Pact(consumer = CONSUMER) public RequestResponsePact createCustomEventPact(PactDslWithProvider builder) { return builder .uponReceiving( "send custom event" ) .path( "/integration-api/api/v1/integration/custom" ) .method( "POST") .matchHeader( "X-Service" , ".+") .matchHeader( "X-Api-Key" , ".+") .body( new PactDslJsonBody() .uuid( "user_id", "6a2f41a3-c54c-fce8-32d2-0324e1c32e22" )) .willRespondWith() .status(HttpStatus.OK.value()) .toPact(); } @Test public void should_send_custom_event() { var dto = new CustomEventDto( "..."); client.sendCustomEvent(dto); } }
  8. Jakub Marchwicki <@kubem> public class TournamentCacheTest { @Rule public ConfiguredTestHazelcastInstance

    hazelcastInstance = new ConfiguredTestHazelcastInstance(); TournamentsSummaryQuery query; @Before public void setup() { this.query = Mockito.mock(TournamentsSummaryQuery .class); } @Test public void scheduledTournament_playerEnters_playerOptedIn () { Tournament t = TournamentFixture .givenATournament() .tournamentId( "tournament1" ) .playerIds( Arrays.asList("playerId", "otherPlayerId" )) .mockWith( query); TournamentCache cache1 = new TournamentCache( query, hazelcastInstance .get()); TournamentCache cache2 = new TournamentCache( query, hazelcastInstance .get()); cache1.handle(new TournamentEnteredEvent( "newPlayer" , t.getTournamentId(), "")); assertThat(cache2.getOptedInPlayersForTournament( t.getTournamentId())) .hasSize( 3) .contains( "newPlayer" ); }
  9. Jakub Marchwicki <@kubem> advanced integration testing Are the IO integration

    correct (saved to database, sent over the wire) Validation of API app provides and consumes Is messaging serialization working with the actual broker?
  10. Jakub Marchwicki <@kubem> @Testcontainers @ExtendWith (SpringExtension .class) @SpringBootTest (webEnvironment =

    SpringBootTest .WebEnvironment .RANDOM_PORT) @Import(PlayerUnblockEventsReplayTest .TestCfg.class) public class PlayerUnblockEventsReplayTest { @LocalServerPort long port; @Autowired RabbitAdmin rabbitAdmin; @Container public static GenericContainer mysqlContainer = new MySQLContainer( "percona:5.6") .withCopyFileToContainer( MountableFile.forClasspathResource("sql/player"), "/docker-entrypoint-initdb.d" ); @Container public static RabbitMQContainer rabbitContainer = new RabbitMQContainer().withQueue( "a_queue"); @Test public void pingPongs() { given().baseUri("http://localhost:" + port).when().get("/ping") .then().body(containsString("pong")).statusCode( 200); } @DynamicPropertySource static void registerProperties (DynamicPropertyRegistry registry) {} static class TestCfg { @Bean @Primary Supplier<Query> createQuery() {} } }
  11. Jakub Marchwicki <@kubem> Path path = Paths.get(ClassLoader.getSystemResource("docker-compose.yml" ).toURI()); Environent environment

    = new DockerComposeContainer( path.toFile()) .withLocalCompose( true) .withExposedService( RABBITMQ, RABBITMQ_PORT, Wait.forListeningPort()) .withExposedService( DATABASE, DATABASE_PORT, Wait.forListeningPort()) .withScaledService( SERVICE_BASE_NAME, SERVICE_NUM_INSTANCES) .withRemoveImages( DockerComposeContainer .RemoveImages.LOCAL); IntStream.rangeClosed(1, SERVICE_NUM_INSTANCES).forEach(i -> { String serviceInstanceName = getServiceInstanceName(i); environment.withExposedService( serviceInstanceName , SERVICE_PORT, Wait.forListeningPort()) .withLogConsumer( serviceInstanceName , new Slf4jLogConsumer( log).withPrefix(serviceInstanceName )); }); environment.start(); log.info("Test environment setup completed successfully" );
  12. Jakub Marchwicki <@kubem> blackbox tests Integration of all layers with

    the application (database, queues) with production configuration Production readiness verification (logs, metrics) Test in a close to production environment
  13. Jakub Marchwicki <@kubem> user driven approach to blackboxes Feature: Process

    customer login information Scenario: A successful customer login Given A user exists When The user logs in Then Audit sub-system is notified with user data