Schreib' die Tests, die du auch lesen magst! · 2019-12-20 · © 2018 andrena objects ag...

Preview:

Citation preview

© 2018 andrena objects ag

Experts in agile software engineering

andrena objects ag

Schreib‘ die Tests, die du auch lesen magst!

6. November 2019

XP Days Germany 2019

Claudia Fuhrmann

André Kappes

© 2019 andrena objects ag

Experts in agile software engineering

Wer von Euch hat…

… schon einmal an einem roten Test gesessen und nicht

verstanden, was er eigentlich bewirken soll?

… diesen Test selbst geschrieben?

2

© 2019 andrena objects ag

Code-Typografie

Experts in agile software engineering 3

Namensgebung

Übersichtlichkeit Prägnanz

Lesbare Tests

© 2019 andrena objects ag

Experts in agile software engineering

Das Projekt

© 2019 andrena objects ag

Experts in agile software engineering

Online - Obstversand

5

Warenkorb

Birne

Apfel

Banane Orange

Ananas Kiwi

Kasse

Vorkasse

PayPal

Rechnung

Kreditkarte

Lastschrift

Preisberechnung

Preisreduzierungen,

z.B. ab der X-ten

Birne kostet jede

weitere nur noch Y €

© 2019 andrena objects ag

Code-Typografie

Experts in agile software engineering 6

Namensgebung

Übersichtlichkeit Prägnanz

Lesbare Tests

© 2019 andrena objects ag

Regeln aus der Typografie

7

• Lange Zeilen bzw. Umbrüche vermeiden

• Durch Absätze strukturieren

• AAA-Pattern zur Orientierung: Gliederung des Tests in Abschnitten nach Arrange – Act – Assert

Experts in agile software engineering

© 2019 andrena objects ag

Experts in agile software engineering

Strukturierung mit dem AAA-Pattern

@Test

void testAdd() {

PreisRepository preisRepository = new PreisRepository();

preisRepository.save(ObstTyp.BIRNE,

PreisStrategyFactory.createNormalPreisStrategy(Money.of(0.70)));

Warenkorb warenkorb = new Warenkorb(preisRepository);

warenkorb.add(5, ObstTyp.BIRNE);

warenkorb.add(3, ObstTyp.BIRNE);

List<Posten> posten = warenkorb.getPosten();

assertEquals(ObstTyp.BIRNE, posten.get(0).getTyp());

assertEquals(8, posten.get(0).getAnzahl());

assertEquals(Money.of(5.60), posten.get(0).getPreis());

}

8

© 2019 andrena objects ag

Experts in agile software engineering

Strukturierung mit dem AAA-Pattern

@Test

void testAdd() {

PreisRepository preisRepository = new PreisRepository();

preisRepository.save(ObstTyp.BIRNE,

PreisStrategyFactory.createNormalPreisStrategy(Money.of(0.70)));

Warenkorb warenkorb = new Warenkorb(preisRepository);

warenkorb.add(5, ObstTyp.BIRNE);

warenkorb.add(3, ObstTyp.BIRNE);

List<Posten> posten = warenkorb.getPosten();

assertEquals(ObstTyp.BIRNE, posten.get(0).getTyp());

assertEquals(8, posten.get(0).getAnzahl());

assertEquals(Money.of(5.60), posten.get(0).getPreis());

}

9

Arrange

Act

Assert

© 2019 andrena objects ag

Code-Typografie

Experts in agile software engineering 10

Namensgebung

Übersichtlichkeit Prägnanz

Lesbare Tests

© 2019 andrena objects ag

Wie soll sie denn heißen? Sprechende Namen finden

*) Variable, Testmethode

*

Experts in agile software engineering

© 2019 andrena objects ag

Experts in agile software engineering

Sprechende Namen finden

class BestellServiceTest {

private KundenRepository repositoryMock = mock(KundenRepository.class);

private BestellService service = new BestellService(repositoryMock);

@Test

void berechnePreis() {

PreisRepository repository = new PreisRepository();

repository.save(BIRNE, reduzierterPreisAb(5, of(0.70), of(0.50)));

Warenkorb input = new Warenkorb(repository);

input.add(5, BIRNE);

BestellUebersicht bU = service.getBestellUebersicht(input);

assertEquals(Money.of(3.30), bU.getGesamtpreis());

}

}

12

© 2019 andrena objects ag

Gute Namen

• Zweck der Variablen / des Felds in diesem Testfall erklären

• Generische Namen vermeiden – spezifische Namen bevorzugen

• Lesbar / aussprechbar

• Je kleiner der Scope, desto kürzer der Name

• Unterscheidbare Namen verwenden

13 Experts in agile software engineering

© 2019 andrena objects ag

Experts in agile software engineering

Magic Values

@Test

void berechnePreis() {

PreisRepository preisRepository = new PreisRepository();

preisRepository.save(BIRNE, reduzierterPreisAb(5, of(0.70), of(0.50)));

Warenkorb mit5Birnen = new Warenkorb(preisRepository);

mit5Birnen.add(5, BIRNE);

BestellUebersicht uebersicht = bestellService.getBestellUebersicht(mit5Birnen);

assertEquals(Money.of(3.30), uebersicht.getGesamtpreis());

}

15

Warum 3.30?

© 2019 andrena objects ag

Experts in agile software engineering

Magic Values

@Test

void berechnePreis() {

PreisRepository preisRepository = new PreisRepository();

preisRepository.save(BIRNE, reduzierterPreisAb(5, of(0.70), of(0.50)));

Warenkorb mit5Birnen = new Warenkorb(preisRepository);

mit5Birnen.add(5, BIRNE);

BestellUebersicht uebersicht = bestellService.getBestellUebersicht(mit5Birnen);

assertEquals(PREIS_FUER_VIER_BIRNEN.plus(REDUZIERTER_PREIS_FUER_FUENFTE_BIRNE),

uebersicht.getGesamtpreis());

}

16

Erklärende Konstanten

© 2019 andrena objects ag

Testmethoden benennen

Namenskonvention von Roy Osherove:

Function/Feature – Input/State – Outcome/Behaviour

17 Experts in agile software engineering

@Test void berechnePreis_reduktionAb5Birnen_wirdBeachtet() { ... }

@Test void berechnePreis () { ... }

© 2019 andrena objects ag

Experts in agile software engineering

Empfehlungen

• Erklärende Namen für lokale Variablen und Felder wählen

• Testmethode aussagekräftig benennen

• Magic Values vermeiden

18

© 2019 andrena objects ag

Code-Typografie

Experts in agile software engineering 19

Namensgebung

Übersichtlichkeit Prägnanz

Lesbare Tests

© 2019 andrena objects ag

Experts in agile software engineering

Prägnanz

private Posten EINE_ANANAS = Posten.of(1, ANANAS, Money.of(3.00));

private Posten EINE_BANANE = Posten.of(1, BANANE, Money.of(2.00));

private Posten EIN_APFEL = Posten.of(1, APFEL, Money.of(1.50));

private Posten EINE_BIRNE = Posten.of(1, BIRNE, Money.of(1.00));

private Posten EINE_KIWI = Posten.of(1, KIWI, Money.of(3.50));

private Posten EINE_ORANGE = Posten.of(1, ORANGE, Money.of(2.50));

private PreisRepository preisRepository;

private KundenRepository kundenRepository;

@BeforeEach

void setUp() {

preisRepository = new PreisRepository();

PreisRepositoryInitializer.create(preisRepository).init();

kundenRepository = new KundenRepository();

}

20

@Test

void testWarenkorb() {

Warenkorb warenkorb = new Warenkorb(preisRepository);

warenkorb.add(1, ANANAS);

warenkorb.add(1, BANANE);

warenkorb.add(1, APFEL);

warenkorb.add(1, BIRNE);

warenkorb.add(1, KIWI);

warenkorb.add(1, ORANGE);

UUID kundenId = warenkorb.getKundenId();

assertThat(kundenId).isNotNull();

List<Posten> posten = warenkorb.getPosten();

assertThat(posten).contains(EINE_ANANAS);

assertThat(posten).contains(EINE_BANANE);

assertThat(posten).contains(EIN_APFEL);

assertThat(posten).contains(EINE_BIRNE);

assertThat(posten).contains(EINE_KIWI);

assertThat(posten).contains(EINE_ORANGE);

}

KundenId setzen

Posten im Warenkorb

Zu lang und unübersichtlich!

© 2019 andrena objects ag

Experts in agile software engineering

Fragen, die ich mir stellen sollte, wenn der Test zu lang ist:

• Kann man den Test aufteilen, weil nicht alles in diesen Test gehört?

• Gehören die einzelnen Asserts in eine andere Testklasse?

• Hat die getestete Klasse zu viele Verantwortlichkeiten?

21

© 2019 andrena objects ag

Experts in agile software engineering

Prägnanz

22

@Test void kundenIdIstNichtNull() { Warenkorb warenkorb = new Warenkorb(preisRepository);

UUID kundenId = warenkorb.getKundenId();

assertThat(kundenId).isNotNull(); } @Test void getPosten() { Warenkorb warenkorb = WarenkorbBuilder.get() .addAnanas(1) .addBananen(1).create();

List<Posten> posten = warenkorb.getPosten();

assertThat(posten).containsExactlyInAnyOrder(EINE_ANANAS, EINE_BANANE); }

Kurze Tests

Wenige Zusicherungen

© 2019 andrena objects ag

Experts in agile software engineering

Empfehlungen

Tests sollten:

• kurz sein

• wenige Zusicherungen haben, am besten nur eine

• in den dazugehörigen Testklassen sein

Wenn dein Test nicht mehr auf deinen Bildschirm passt, ist er definitiv zu lang!

23

© 2019 andrena objects ag

Experts in agile software engineering

Sprechende Zusicherungen und Fehlermeldungen

@Test

void testZahlungsService() {

BestellUebersicht ueberLimit = mock(BestellUebersicht.class);

when(ueberLimit.getGesamtpreis()).thenReturn(UEBER_LIMIT_FUER_RECHNUNG);

ZahlungsService zahlungsService = new ZahlungsService(new KundenRepository());

List<ZahlungsArt> angeboteneZahlungsArten = zahlungsService.getZahlungsarten(ueberLimit);

assertTrue(!angeboteneZahlungsArten.contains(ZahlungsArt.RECHNUNG));

}

24

Negation Keine Informationen über den Grund eines Fehlschlags

© 2019 andrena objects ag

Experts in agile software engineering

Sprechende Zusicherungen und Fehlermeldungen

@Test

void testZahlungsService() {

BestellUebersicht ueberLimit = mock(BestellUebersicht.class);

when(ueberLimit.getGesamtpreis()).thenReturn(UEBER_LIMIT_FUER_RECHNUNG);

ZahlungsService zahlungsService = new ZahlungsService(new KundenRepository());

List<ZahlungsArt> angeboteneZahlungsArten = zahlungsService.getZahlungsarten(ueberLimit);

assertTrue(!angeboteneZahlungsArten.contains(ZahlungsArt.RECHNUNG),"Zahlart 'Rechnung‘ nicht enthalten!");

}

25

Aussagekräftiger Kommentarstring

© 2019 andrena objects ag

Experts in agile software engineering

Sprechende Zusicherungen und Fehlermeldungen

@Test

void testZahlungsService() {

BestellUebersicht ueberLimit = mock(BestellUebersicht.class);

when(ueberLimit.getGesamtpreis()).thenReturn(UEBER_LIMIT_FUER_RECHNUNG);

ZahlungsService zahlungsService = new ZahlungsService(new KundenRepository());

List<ZahlungsArt> angeboteneZahlungsArten = zahlungsService.getZahlungsarten(ueberLimit);

assertThat(angeboteneZahlungsArten).doesNotContain(ZahlungsArt.RECHNUNG);

}

26

Assertj Matcher

© 2019 andrena objects ag

Experts in agile software engineering

Empfehlungen

• Zusicherungen möglichst präzise formulieren

• Auf sprechende Fehlermeldung im Testfehlschlags-Fall achten

27

© 2019 andrena objects ag

Code-Typografie

Experts in agile software engineering 28

Namensgebung

Übersichtlichkeit Prägnanz

Lesbare Tests

© 2019 andrena objects ag

Experts in agile software engineering

Hin- und Herspringen

public class BestellUebersichtTest extends WarenkorbTest { private Posten EINE_BANANE = Posten.of(1, BANANE, Money.of(2.00)); private Posten EINE_ANANAS = Posten.of(1, ANANAS, Money.of(3.00)); private PreisRepository preisRepository = new PreisRepository(); private KundenRepository kundenRepository; private BestellService underTest; @BeforeEach void setUp() { PreisRepositoryInitializer.create(preisRepository).init(); underTest = new BestellService(kundenRepository); }

29

Wie sehen die Preise aus?

Warum? Was gibt es dort?

© 2019 andrena objects ag

Experts in agile software engineering

Hin- und Herspringen

@Test void getBestellUebersicht_eineAnanasUndEineBanane_BestelluebersichtOk() { Warenkorb warenkorb = createWarenkorbWithAnanasUndBanane(preisRepository);

BestellUebersicht bestellUebersicht = underTest.getBestellUebersicht(warenkorb);

assertBestellUebersicht(bestellUebersicht, warenkorb.getKundenId(), Money.of(5.00), EINE_ANANAS, EINE_BANANE); } private void assertBestellUebersicht(BestellUebersicht bestellUebersicht, UUID kundenId, Money preis, Posten... posten) { assertThat(bestellUebersicht.getGesamtpreis()).isEqualTo(preis); assertThat(bestellUebersicht.getPosten()).containsExactlyInAnyOrder(posten); assertThat(bestellUebersicht.getKundenId()).isEqualTo(kundenId); }

30

Wo finde ich diese Methode?

Was testet dieses Assert?

© 2019 andrena objects ag

Experts in agile software engineering

Hin- und Herspringen

public class BestellServiceTest {

private Money PREIS_EINE_ANANAS = Money.of(3.00);

private Money PREIS_EINE_BANANE = Money.of(2.00);

private Posten EINE_ANANAS = Posten.of(1, ANANAS, PREIS_EINE_ANANAS);

private Posten EINE_BANANE = Posten.of(1, BANANE, PREIS_EINE_BANANE);

private KundenRepository kundenRepository;

private BestellService underTest;

@BeforeEach

void setUp() {

kundenRepository = new KundenRepository();

underTest = new BestellService(kundenRepository);

}

31

Keine magische Preiserstellung

© 2019 andrena objects ag

Experts in agile software engineering

Hin- und Herspringen

@Test

void getBestellUebersicht_eineAnanasUndEineBanane_BestelluebersichtOk() {

Warenkorb warenkorb = WarenkorbBuilder.warenkorb().withKundenId(KUNDEN_ID)

.addAnanas(1, PREIS_EINE_ANANAS)

.addBananen(1, PREIS_EINE_BANANE).create();

BestellUebersicht bestellUebersicht = underTest.getBestellUebersicht(warenkorb);

assertThat(bestellUebersicht)

.hasGesamtpreis(PREIS_EINE_ANANAS.plus(PREIS_EINE_BANANE))

.hasPosten(EINE_ANANAS, EINE_BANANE)

.hasKundenId(KUNDEN_ID);

}

32

Builder, der den Warenkorb samt Preise erstellt

Custom matcher

© 2019 andrena objects ag

Experts in agile software engineering

Empfehlungen und Lösungsansätze

• Relevantes sichtbar machen, Irrelevantes verbergen

• Builder Pattern

• Custom Matchers

33

© 2019 andrena objects ag

Experts in agile software engineering

Noise stört die Lesbarkeit

@Test

void testAdd() {

PreisRepository preisRepository = new PreisRepository();

preisRepository.save(ObstTyp.BIRNE,

PreisStrategyFactory.createNormalPreisStrategy(Money.of(0.70)));

Warenkorb warenkorb = new Warenkorb(preisRepository);

warenkorb.add(5, ObstTyp.BIRNE);

warenkorb.add(3, ObstTyp.BIRNE);

List<Posten> posten = warenkorb.getPosten();

assertEquals(ObstTyp.BIRNE, posten.get(0).getTyp());

assertEquals(8, posten.get(0).getAnzahl());

assertEquals(Money.of(5.60), posten.get(0).getPreis());

}

34

© 2019 andrena objects ag

Experts in agile software engineering

Weniger Noise durch Syntactic Sugar

@Test

void testAdd() {

PreisRepository repository = new PreisRepository();

repository.save(BIRNE, normalPreis(0.70));

Warenkorb warenkorb = new Warenkorb(repository);

warenkorb.add(5, BIRNE);

warenkorb.add(3, BIRNE);

Posten soleItem = warenkorb.getPosten().get(0);

assertEquals(BIRNE, soleItem.getTyp());

assertEquals(8, soleItem.getAnzahl());

assertEquals(Money.of(5.60), soleItem.getPreis());

}

35

Static imports verwenden

Erklärende Variablen einführen

private PreisStrategy normalPreis(double value) { return PreisStrategyFactory.createNormalPreisStrategy(of(value)); }

Syntactic sugar-Methoden extrahieren

© 2019 andrena objects ag

Experts in agile software engineering 36

Zusammenfassung

© 2019 andrena objects ag

Code-Typografie

Experts in agile software engineering 37

Namensgebung

Übersichtlichkeit Prägnanz

Lesbare Tests

Kümmere Dich um Eure Tests!

Nimm Dein Team mit!

© 2019 andrena objects ag

Experts in agile software engineering

Referenzen

Bücher

• Roy Osherove, The Art of Unit Testing (Manning, 2013)

• Gerard Meszaros, xUnit Test Patterns: Refactoring Test Code (Addison Wesley, 2007) http://xunitpatterns.com/

Blogs

• Petri Kainulainen, Writing Clean Tests https://www.petrikainulainen.net/writing-clean-tests/

• Thomas Countz, Essential & Relevant: A Unit Test Balancing Act https://8thlight.com/blog/thomas-countz/2019/02/19/essential-and-relevant-unit-tests.html

38

© 2019 andrena objects ag

Vielen Dank!

Experts in agile software engineering

© 2019 andrena objects ag

Experts in agile software engineering

Euer Feedback

Schreib‘ die Tests, die du auch lesen magst!

Recommended