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

Voxxed Days Brussels 2023 - Your Tests also nee...

Avatar for Jonas Geiregat Jonas Geiregat
May 23, 2023
140

Voxxed Days Brussels 2023 - Your Tests also need some Architecting

Avatar for Jonas Geiregat

Jonas Geiregat

May 23, 2023

Transcript

  1. The reality of tests - Brittle - Hard to maintain

    - Complex - Cryptic - Did not guide - Slow - Skipped
  2. Engineers love solving problems, that have zero business value because

    it’s considered fun. Why do we write code ?
  3. “The trigger for writing a test is not a new

    class nor a new public method” -- Ian Cooper - TDD, Where Did It All Go Wrong
  4. The main reason for a test to exists is because

    there is a requirement that describes a behavior of the system
  5. So you want us to only write integration tests ?

    -- Fellow developers - coffee machine
  6. unit tests end to end tests integration tests slow /

    expensive fast / cheap Does the classical testing Pyramid still stand ?
  7. The type of test: unit, integration, acceptance etc .. is

    how you are testing. It is a detail, that is seldom relevant.
  8. integrated impl. detail tests integration behavior / feature / use-case

    tests How vs What end to end tests unit tests integration tests Microservices testing honeycomb Kent C. Dodds pyramid static static
  9. Test should not be viewed in the same manner one

    views production code. It’s a different context. Different rules are at play.
  10. Keep your readers in your test method Minimize setup and

    tear-down logic to only the technical
  11. Classic test naming convention class InvoiceServiceTest { VS Test naming

    convention based on requirement/feature/behavior class PayInvoiceFeatureTest { class MarkInvoiceAsProcessedFeatureTest { class CreateInvoiceTemplateFeatureTest { class ProcessInvoiceFeatureTest { class AddInvoiceLineFeatureTest {
  12. Do not communicate what you are testing @Test void testExpireInvoice()

    { @Test void testStartReportJob() { @Test void addPineappleToppingToPizzaTest() {
  13. Don’t communicate what you are testing but why you need

    a test The why captures the behaviour
  14. The why @Test void testExpireInvoice() { @Test void unExpiredInvoice_shouldExpire_after30DaysAfterPublicationDate() {

    @Test void testStartReportJob() { @Test void startingANewReportJob_shouldUpdateTheStateToRunning() { @Test void addPineappleToppingToPizzaTest() { @Test void pineappleIsAnIllegalTopping_andShouldPreventTheCookFromAddingIt() {
  15. @Test void shouldDetectValidIds() { Map<String, Object> fields = FieldsGenerator.withValidIds(); Map<String,

    Identifier> resolvedIdentifiers = resolver.resolve(fields); assertThat(resolvedIdentifiers).containsKey("TIN"); } Let your test document the full story avoiding layers or abstractions
  16. @ParameterizedTest @ValueSource(strings = {"TIN", "LID", "PID"}) void shouldResolveValidIds(String validId) {

    var fields = Map.of( validId, "586-547-145", "SSN", "6589-8745-1547" ) Map<String, Identifier> resolvedIdentifiers = resolver.resolve(fields); assertThat(resolvedIdentifiers).containsKey(validId); } Let your test document the full story avoiding layers or abstractions
  17. Address billingAddress = new Address.Builder() .streetAddress("123 Main St") .city("Anytown") .state("CA")

    .zipCode("12345") .country(Country.US) .build(); Address shippingAddress = new Address.Builder() .streetAddress("456 Oak Ave") .city("Othertown") .state("CA") .zipCode("67890") .country(Country.US) .build(); List<InvoiceItem> items = new ArrayList<>(); items.add(new InvoiceItem("Product A", 100.00, Tax.vatPercentage(21))); items.add(new InvoiceItem("Product B", 100.00, Tax.vatPercentage(21))); Customer customer = new Customer.Builder() .name("John Doe") .email("[email protected]") .phoneNumber("555-123-4567") .build(); Invoice invoice = new Invoice.Builder() .invoiceNumber("001") .customer(customer) .billingAddress(billingAddress) .shippingAddress(shippingAddress) .invoiceDate(LocalDate.now()) .items(items) .build(); var amount = invoice.getVatAmount(); assertThat(amount).isEqualTo(Amount.EUR(42))
  18. // given Address billingAddress = new Address.Builder() .streetAddress("123 Main St")

    .city("Anytown") .state("CA") .zipCode("12345") .country(Country.USA) .build(); Address shippingAddress = new Address.Builder() .streetAddress("456 Oak Ave") .city("Othertown") .state("CA") .zipCode("67890") .country(Country.USA) .build(); List<InvoiceItem> items = new ArrayList<>(); items.add(new InvoiceItem("Product A", 100.00, Tax.vatPercentage(21))); items.add(new InvoiceItem("Product B", 100.00, Tax.vatPercentage(21))); Customer customer = new Customer.Builder() .name("John Doe") .email("[email protected]") .phoneNumber("555-123-4567") .build(); Invoice invoice = new Invoice.Builder() .invoiceNumber("001") .customer(customer) .billingAddress(billingAddress) .shippingAddress(shippingAddress) .invoiceDate(LocalDate.now()) .items(items) .build(); // when var amount = invoice.getVatAmount(); // then assertThat(amount).isEqualTo(Amount.EUR(42))
  19. InvoiceMother.validInvoice(); InvoiceMother.validInvoiceWithUSShippingAddress(); InvoiceMother.validInvoiceFromYesterday(); InvoiceMother.validInvoiceWithInvoiceLines(10); InvoiceMother.validInvoice() <InvoiceMother.Builder> .withInvoiceItems( new InvoiceItem("Product A",

    Amount.EUR(100), Tax.vatPercentage(21)), new InvoiceItem("Product B", Amount.EUR(100), Tax.vatPercentage(21))) .build(); <Invoice> Limit your factory methods, override what matters with setters InvoiceMother.validInvoice() .withShippingAddress(address -> address.country(Country.US)) .build() Emphasize what matters and hide the irrelevant details
  20. //given var invoice = InvoiceMother.validInvoice() .withInvoiceItems( new InvoiceItem("Product A", Amount.EUR(100),

    Tax.vatPercentage(21)), new InvoiceItem("Product B", Amount.EUR(100), Tax.vatPercentage(21))) .build(); // when var amount = invoice.getVatAmount(); // then assertThat(amount).isEqualTo(Amount.EUR(42)) Emphasize what matters and hide the irrelevant details
  21. org.opentest4j.AssertionFailedError: Expecting value to be true but was false Expected

    :true Actual :false assertThat(invoice.isPaid()).isTrue();
  22. org.opentest4j.AssertionFailedError: Expecting invoice to be paid but was not. Expected

    :true Actual :false Assertions.assertThat(actual.isPaid()) .withFailMessage("Expecting invoice to be paid but was not.") .isTrue();
  23. class InvoiceAssertions extends AbstractAssert<InvoiceAssertions, Invoice> { protected InvoiceAssertions(Invoice invoice) {

    super(invoice, InvoiceAssertions.class); } public static InvoiceAssertions assertThat(Invoice invoice) { return new InvoiceAssertions(invoice); } public InvoiceAssertions isPaid() { Assertions.assertThat(actual.isPaid()) .withFailMessage("Expecting invoice to be paid but was not.") .isTrue(); return this; } }
  24. //given InvoiceMother.validInvoice() .withInvoiceItems( new InvoiceItem("Product A", Amount.euro(100.00), Tax.vatPercentage(21)), new InvoiceItem("Product

    B", Amount.euro(100.00), Tax.vatPercentage(21))) .build(); // when var amount = invoice.getVATAmount(); // then assertThat(invoice).hasAVatAmountOf(Amount.euro(42.00))
  25. Test and production code should be equal citizens looked at

    from different context with equal architectural rights cared for equally equally important