Unit tests en pasta

Pagina: 1
Acties:

Onderwerpen


  • YopY
  • Registratie: September 2003
  • Laatst online: 13-07 01:14
Dus. Ik ben op het moment lekker bezig om unit tests voor mijn programma te schrijven, en tegelijkertijd de zaak te refactoren (wat in dit geval het testen makkelijker maakt, maar mijns insziens het programma zelf ook beter ontworpen maakt).

Nu heb ik in mijn class PietjePuk een functie doeIets(), die op zijn beurt de private doeIetsAnders() aanroept:

Java:
1
2
3
4
5
6
7
8
9
10
class PietjePuk {
  public void doeIets() {
    // doe iets!
    doeIetsAnders();
  }

  public void doeIetsAnders() {
    // doe iets anders
  }
}


Nu ben ik bezig met twee dingen:

1. Vaststellen dat doeIets() ook inderdaad doeIetsAnders() aanroept
2. De test zo eenvoudig mogelijk houden.

Punt 1 moet, bij een goeie test, niet horen, da's bekend. Puntje 2 zit ik echter een beetje mee - stel dat doeIetsAnders() een hele trits aan operaties uitvoert, die ook weer andere zooi uitvoert, waardoor ik heel veel code in mijn tests moet schrijven die niet direct met doeIets() te maken hebben (dwz code die ervoor zorgt dat doeIetsAnders() niet mislukt).

Om puntje 2 aan te pakken, heb ik nu de functionaliteit van doeIetsAnders() uit class PietjePuk gehaald en in zijn eigen class gezet, we noemen het HenkieCola. PietjePuk krijgt nu een instantie van de nieuwe class. Zo kan ik een mock object maken (JMock) van HenkieCola, die toekennen aan PietjePuk, en doeIets() aanroepen. Met behulp van jMock geef ik aan dat doeIets() altijd doeIetsAnders() in HenkieCola aan moet roepen, zodat de test voor doeIets() eenvoudiger wordt. Ook kan ik de tests voor HenkieCola nu in een eigen test unit stoppen.

Genoeg pietjes en henken nu, even concreet weer.

Mijn vraag is nu: Is dit een wijze aanpak? Of zorg ik er hiermee voor dat ik enorme stukken lasagne code schrijf?

Om bijvoorbeeld nu een Servlet z'n doGet() methode eenvoudiger te maken, die eerder vier private functies aanriep (create session, handle request, log exception, close session), heb ik nu een SessionManager, een RequestHandler, en een ExceptionLogger class geschreven, die geinstantieerd en aan de servlet toegekend.

Is dit slim? Of ben ik nu flink bezig om aan over-engineering te doen? Lasagne aan het bakken of (zoals het hoort) nette tortellini aan het maken? Spaghetti is het in ieder geval niet... meer, zover heb ik het al gerefactored.

Wat vinden jullie hiervan? Onderstaand nog een diagrammetje voor de volledigheid - links de originele, rechts de gerefactorde.

Afbeeldingslocatie: http://img264.imageshack.us/img264/3959/diagram.png

Acties:
  • 0 Henk 'm!

  • bigbeng
  • Registratie: Augustus 2000
  • Laatst online: 26-11-2021
Persoonlijk vind ik het er best aardig uitzien. Het is lastig te zeggen of het handig is of niet, omdat ik dan aannames moet gaan doen over de rest van je code.

In zijn algemeenheid gebruik een aantal richtlijnen voor mijzelf om refactoring toe te passen:
- Verbeteren van de leesbaarheid van mijn code.
- Reduceren van code duplicatie
- Verwijderen van "code smells" (theoretisch is het vorige punt een code smell, maar belangrijk genoeg om apart te noemen denk ik)

Er zijn nog legio andere redenen te bedenken, maar dit zijn mijn voornaamste drivers

Acties:
  • 0 Henk 'm!

  • YopY
  • Registratie: September 2003
  • Laatst online: 13-07 01:14
bigbeng schreef op vrijdag 27 november 2009 @ 00:49:
Persoonlijk vind ik het er best aardig uitzien. Het is lastig te zeggen of het handig is of niet, omdat ik dan aannames moet gaan doen over de rest van je code.

In zijn algemeenheid gebruik een aantal richtlijnen voor mijzelf om refactoring toe te passen:
- Verbeteren van de leesbaarheid van mijn code.
- Reduceren van code duplicatie
- Verwijderen van "code smells" (theoretisch is het vorige punt een code smell, maar belangrijk genoeg om apart te noemen denk ik)

Er zijn nog legio andere redenen te bedenken, maar dit zijn mijn voornaamste drivers
Ben ik ook mee bezig, maar met een extra punt daarbij: Unit tests schrijven. Ik ervaar bij mijzelf dat als ik een goeie, korte en makkelijke unit test kan schrijven, dat mijn code ook goed is / moet zijn.

Ik bedoel, als ik eerder een test zou moeten schrijven voor mijn request handler (zoals hierboven beschreven), dan zou ik de test zo moeten schrijven dat de verschillende (private) functies niet mislukken en het goeie resultaat krijgen, resultaat, sowieso een honderd regels code. beuh. Daar ik lui ben, maar wél een unit test wil hebben, heb ik voor de makkelijkste aanpak gekozen: refactoren zodat de request handler onafhankelijk van private functies werkt (en de test daar geen rekening mee hoeft te houden). Resultaat:

http://pastebin.org/57727

Netjes, goed leesbaar, pro. Zijn nog drie variaties daarvan, één die geen session maakt (waarbij sessionManager.closeSession() niet aangeroepen wordt), en een waar handleRequest een exception veroorzaakt.

Maar da's wat anders, dit topic is over of ik het niet te ver doortrek. Waar er eerst twee classes waren (een servlet en een component dat de servlet initialiseert), zijn er nu al 11 bestanden, een deel interfaces, een deel implementaties daarvan, die zoals het nu lijkt niet hergebruikt zullen worden (of ik moet ze in een aparte library gaan zetten en ze in andere projecten gebruiken) (of ik moet gewoon een goeie library die door slimmere personen gemaakt is gaan gebruiken). Overbodig?

Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 22:19
[b][message=32997495,noline]den.

Puntje 2 zit ik echter een beetje mee - stel dat doeIetsAnders() een hele trits aan operaties uitvoert, die ook weer andere zooi uitvoert, waardoor ik heel veel code in mijn tests moet schrijven die niet direct met doeIets() te maken hebben (dwz code die ervoor zorgt dat doeIetsAnders() niet mislukt).
Misschien is dat wel een indicatie dat 'doeIetsAnders' veel te veel doet ? Maw, misschien is dat een indicatie dat je doeIetsAnders misschien moet opsplitsen in een aantal andere methods, en misschien is het ook wel een indicatie dat doeIetsAnders helemaal niet private moet zijn ?
Om puntje 2 aan te pakken, heb ik nu de functionaliteit van doeIetsAnders() uit class PietjePuk gehaald en in zijn eigen class gezet, we noemen het HenkieCola. PietjePuk krijgt nu een instantie van de nieuwe class. Zo kan ik een mock object maken (JMock) van HenkieCola, die toekennen aan PietjePuk, en doeIets() aanroepen. Met behulp van jMock geef ik aan dat doeIets() altijd doeIetsAnders() in HenkieCola aan moet roepen, zodat de test voor doeIets() eenvoudiger wordt. Ook kan ik de tests voor HenkieCola nu in een eigen test unit stoppen.
zie je wel...

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • bigbeng
  • Registratie: Augustus 2000
  • Laatst online: 26-11-2021
YopY schreef op vrijdag 27 november 2009 @ 09:24:
[...]


Ben ik ook mee bezig, maar met een extra punt daarbij: Unit tests schrijven. Ik ervaar bij mijzelf dat als ik een goeie, korte en makkelijke unit test kan schrijven, dat mijn code ook goed is / moet zijn.

...
Unit tests zijn m.i. essentieel om te verifieren dat code na een refactoring nog werkt. Deze spreken voor mij dus voor zich :)

Acties:
  • 0 Henk 'm!

  • YopY
  • Registratie: September 2003
  • Laatst online: 13-07 01:14
Dat ook natuurlijk. Echter doe ik met deze app liever unit tests + refactoring tegelijk, daar tests schrijven voor een slecht gestructureerd programma nogal veel werk is.

Overigens weet ik niet meer waarvoor ik dit topic geschreven heb. Ben ik goed bezig? Denk het wel. Draaf ik door? Een beetje.

Acties:
  • 0 Henk 'm!

  • Remus
  • Registratie: Juli 2000
  • Laatst online: 15-08-2021
De grote vraag is natuurlijk: is het belangrijk dat doeIets() doeIetsAnders() aanroept. Ik denk het niet, en dat is ook niet iets dat je met een unit-test wilt controleren IMO. Met de unittest moet je controleren of het eindresultaat van de methode aanroep correct is (dus: zijn de relevante object-states gewijzigd, krijg ik het correcte resultaat terug). In andere woorden: met bepaalde invoer komt er de juiste uitvoer uit.

Wat je nu doet is controleren of je implementatie inderdaad je implementatie is (dus de call-flow). En zelfs als je dat wil: gebruik dan een static analysis tool.
Pagina: 1