Share
Embed
Modern Testing Tools for Java Developers
Elias Nogueira
May 30, 2023
Transcript
36 slides
-
ELIAS NOGUEIRA Senior Principal Software Engineer Brazil Oracle Ace Java
Magazine NL Editor Java Champion -
AssertJ Assertion library that provides extensive assertion methods, adding readability
to the tests. In a fluent way, you can assert: • Common types: Optional, Predicates, Streams, Iterable … • Primitive types • Java 8 Temporal Types • Atomic Types -
AssertJ The cool features • Recursive Comparison by Field •
Soft Assertions • Custom Assertions -
AssertJ The cool features: Recursive Comparison by Field @Test void
recursiveComparisonTest() { var simulation1 = Simulation.builder().name("John").cpf("9582728395") .email("[email protected]").amount(new BigDecimal("500")).installments(1) .insurance(false).build(); var simulation2 = SimulationDto.builder().name("John").cpf("9582728395") .email("[email protected]").amount(new BigDecimal("500")).installments(1) .insurance(false).build(); // ✅ won't fail because the assertion is done field by field assertThat(simulation1).usingRecursiveComparison().isEqualTo(simulation2); // ❌ will fail because equals compare references assertThat(simulation1).isEqualTo(simulation2); } -
AssertJ The cool features: Soft Assertions @Test void softAssertionTest() {
var simulation = Simulation.builder().name("John").cpf("9582728395") .email("[email protected]").amount(new BigDecimal("500")) .installments(1).insurance(false).build(); SoftAssertions.assertSoftly(softly -> { softly.assertThat(simulation.getName()).isEqualTo("John"); softly.assertThat(simulation.getInsurance()).isTrue(); softly.assertThat(simulation.getAmount()).isGreaterThan(new BigDecimal("2.00")); }); } -
AssertJ The cool features: Custom Assertions @Test void simulationValidationErrorNormalAssertion() {
var simulation = Simulation.builder().name("John").cpf("9582728395") .email("[email protected]").amount(new BigDecimal("500")) .installments(1).insurance(false).build(); SimulationAssert.assertThat(simulation).hasValidInstallments(); SimulationAssert.assertThat(simulation).hasValidAmount(); // never gets here } -
Examples Test • Test class using both Custom and Custom
Soft Assertion Custom Assertion • Custom assertion methods based on a context (Simulation) Scalable one • Custom Soft Assertion using the the Custom Assertion -
REST-Assured Most powerful API Testing library for Java, which is
a DSL to simplify testing of REST based services based on the given-when-then keywords (but it’s not BDD). class RestAssuredExampleTest { @Test void welcome() { given() .param("name", "Elias"). when() .post("/register"). then() .statusCode(200) .body("message", is("")); } } -
REST-Assured You can have multiple tests like this, but it
won’t scale! @Test @DisplayName("Should validate all existing simulations") void getAllExistingSimulations() { var existingSimulations = SimulationDataFactory.allExistingSimulations(); var simulationsRequested = when(). get("/simulations"). then(). statusCode(SC_OK). extract(). as(Simulation[].class); Assertions.assertThat(existingSimulations).contains(simulationsRequested); } -
REST-Assured We can fast create the architecture and decrease the
test maintenance by: • Using the openapi-generator to create the client based on REST- Assured • Create a service layer in the test project -
Examples Basic one (don’t scale) • Raw usage Intermediate one
• Request and Response Specification Scalable one • Service Abstraction + OpenAPI Generator -
Awaitility DSL that allows you to express expectations of an
asynchronous system http://www.awaitility.org 3.4k ⭐ on GitHub -
Awaitility DLS to solve the problem, during the test, to
wait for an asynchronous system (RabbitMQ, Apache Kafka, Redis, and others) to complete the request. It has the ability to: • create conditions to wait • check fields • support Atomic structures • ignoring and checking exceptions • integrate with AssertJ -
Awaitility The test will fail because the event will take
some time to complete and there’s no indication of waiting for something to happen. @Test void checkEventWithRestriction() { String cpf = "26276298085"; checkForRestriction(cpf); given().pathParam("cpf", cpf).when().get("/events/{cpf}").then().statusCode(SC_OK); } -
Awaitility You can use the Repository object, or a SQL
query, to check for the data, and then continue the code execution. @Test void checkEventWithRestriction_Success() { String cpf = "55856777050"; checkForRestriction(cpf); await().atMost(10, SECONDS).until(eventIsFound(cpf)); given().pathParam("cpf", cpf).when().get("/events/{cpf}").then().statusCode(SC_OK); } // 👇 the Callable will do the trick public Callable<Boolean> eventIsFound(String cpf) { return () -> eventRepository.findByCpf(cpf).isPresent(); } -
Examples How to see, locally, the delay in the database
insertion • Use the event-source branch • Start the application • Login to H2 (sa/password) • Run a query into the EVENT_SOURCE table • Make a POST request (you can use the Swagger UI) • Run a query into the EVENT_SOURCE table • The insertion will occur only 7 seconds after the request -
Examples Running the example • Test class: EventTest • Run
the checkEventWithRestriction_Fail() and see it failing because of the 7 second delay • Run the checkEventWithRestriction_Success() and see the test green Disclaimer There’s no asynchronous system here. A delay of 7 seconds was added in the EventService that will be called by the RestrictionController methods and SimulationsController for the POST and DELETE methods. -
WireMock One of the most popular mock open-source tools for
API mock testing. You probably know it by the isolated usage in your unit tests, like this: @WireMockTest public class WiremockTest { @Test void testUsingWiremock() { String response = """ { "error": "The cpf 987654321 as a restriction" } """; stubFor((get(urlEqualTo("/restriction")) .withQueryParam("cpf", equalTo("987654321")) .willReturn( aResponse().withResponseBody(response)))); } } -
WireMock Instead of mocking something you need, that will be
duplicated across the teams, you can implement a Service Virtualization approach using WireMock to share the same mock across multiple teams. Team 1 Team 2 Team N Service Virtualization Standalone local container Deploy in a k8s infra -
pitest What is Mutation Testing? Is the approach to add
faults in your code based on a series of mutants (specific code changes) showing you if your code has lived or killed mutations. • Lived mutations: indicates you have something to work on :) • Killed mutations: means that the change applied is covered -
pitest Why Mutation Testing? Because the normal test coverage approach
measures only the executed code. It adds the support of fault detections which are conditions we haven't added in the tests, mostly corner cases. There're a lot of mutators, and the most common ones are: • Conditional Boundary • Increment • Math