onsdag 5 december 2012

Sammanfattning av TDD-kurs

För två veckor sedan (v.47) var jag iväg på en två dagars TDD-kurs. Har under de senaste 2-3 åren försökt att jobba mycket med TDD för att jag i många fall fått bevisat att kvalitén höjs och kodbasen blir mer lättarbetad.

Tänkte i detta inlägg gå igenom några delar av det vi gick igenom under kursen.
Eftersom jag redan var såld på TDD innan kursen var mitt mål att:
  • Få mer kunskap inom området
  • Få mer kunskap om agil utveckling
  • Få mer argument om varför TDD ska användas
  • Lära mig skriva tydligare kod (för människor)
  • Lära mig mer om testramverk
  • Lära mig att refaktorera bättre
Del 1 - Agil utveckling
Kursen började med att vi gick igenom agil utveckling (Agile Manifesto) och hur man arbetar agilt enligt "The twelve principles of Agile software development. Samt varför detta fungerar. Även agila metodiker som Lean Software Development och Scrum togs upp. Kursledaren tipsade också om att alla som inte kör Scrum (det är inte säkert att man kan köra Scrum fullt ut i alla projekt) åtminstone borde försöka införa dagliga stå-upp möten där varje person i teamet ska besvara följande tre frågor:

1.) Vad gjorde du igår?
2.) Vad ska du göra idag?
3.) Är det något som hindrar dig?

Genom dessa frågor får teamet grepp om den aktuella statusen för projektet, och kan sätta in åtgärder om någon kört fast.

Efter introduktionen gick vi igenom Extreme Programming (XP). Tidigare trodde jag att XP endast stod för parprogrammering, men det står också för:
  • Fokus på koden - koden ska vara i centrum
  • Par / (ping-pong) programmering
  • Code standards (kommunikation genom kod)
    • Resharper
    • Stylecop
  • Att slippa jobba dubbelt inför projektavslut (eftersträva 40 timmars arbetsvecka)
  • Testning!
  • Enkel design (minsta möjliga ändring för att få testet att passera)
  • Kollektivt ägande av koden (vem som helst kan ändra koden i systemet när som helst)
  • Refaktorera (hela tiden)
  • Continious integration (för att undvika "works on my machine")
  • Lyssna  (på kunden/beställaren)
(Fler punkter nämndes säkert också, jag kan ha missat några)

Eftersom allting nämndes på några få timmar, kan jag inte påstå att allting fastnade. Dock är det bra att ha hört om dessa metodiker och känna till detta inför framtida projekt.

Del 2 - User stories
En user story definierar kort och gott vad systemet ska göra.

Som en ________
så vill jag ________
så att ________

Exempel:

Som en bankkund
så vill jag kunna logga in på internetbanken
så att jag kan betala mina räkningar

En user story bryts sedan när till en programmeringsuppgift (programming task).
Uppgiften är färdig när enhetstestet passerar.

Efter detta gjorde vi en övning där vi skapade tre egna user stories som vi sedan fick bryta ner till programmeringsuppgifter.

Del 3 - Test Driven Development
Denna del började med att titta på egenskaper för ett bra enhetstest. Dessa var:
  • Fristående - Enhetstester ska inte ha beroenden till andra resurser (databaser, filsystem etc.)
  • Oberoende - Enhetstester och metoder i testklasserna ska inte vara beroende av varandra
  • Genomgående - Ska täcka alla kritiska fall (100% code cover är dock inget att eftersträva)
  • Professionella - Testkod ska följa samma kodstandard och kvalité som den övriga applikationen. Annars går den inte att underhålla.
Följt av argument för TDD:
  • Högre produktivitet
  • Högre kvalité
  • Bättre design
  • Möjlighet att införa sena förändringar
  • Bättre dokumentation och kommunikation
  • Uppmuntrar utvecklare till att skriva kod som är enkelt att testa (Dependency Injection)
  • Mindre tid behöver läggas på felsökning (debug)
Under denna del av kursen gjorde vi även en hel del labbar och TDD coding katas.
Här är string calculator katan vi gjorde:

String Calculator
  1. Create a simple String calculator with a method int Add(string numbers)
    1. The method can take 0, 1 or 2 numbers, and will return their sum (for an empty string it will return 0) for example “” or “1” or “1,2”
    2. Start with the simplest test case of an empty string and move to 1 and two numbers
    3. Remember to solve things as simply as possible so that you force yourself to write tests you did not think about
    4. Remember to refactor after each passing test
  2. Allow the Add method to handle an unknown amount of numbers
  3. Allow the Add method to handle new lines between numbers (instead of commas).
    1. the following input is ok:  “1\n2,3”  (will equal 6)
    2. the following input is NOT ok:  “1,\n” (not need to prove it - just clarifying)
    1. Support different delimiters
    2. to change a delimiter, the beginning of the string will contain a separate line that looks like this:   “//[delimiter]\n[numbers…]” for example “//;\n1;2” should return three where the default delimiter is ‘;’ .
    3. the first line is optional. all existing scenarios should still be supported
  4. Calling Add with a negative number will throw an exception “negatives not allowed” - and the negative that was passed.if there are multiple negatives, show all of them in the exception message
    stop here if you are a beginner. Continue if you can finish the steps so far in less than 30 minutes.
  5. Numbers bigger than 1000 should be ignored, so adding 2 + 1001  = 2
  6. Delimiters can be of any length with the following format:  “//[delimiter]\n” for example: “//[***]\n1***2***3” should return 6
  7. Allow multiple delimiters like this:  “//[delim1][delim2]\n” for example “//[*][%]\n1*2%3” should return 6.
  8. make sure you can also handle multiple delimiters with length longer than one char

Här är mitt lösningsförslag (som inte redigerats sedan dess, trots att det garanterat kan göras betydligt bättre):
Testklass: https://gist.github.com/4117661
Implementation: https://gist.github.com/4117657

Övningar och kator kördes i en miljö bestående av Visual Studio 2010ReSharper och NUnit samt Rhino Mocks eller Moq. Dock var inga av verktygen tvingande (en kursdeltagare körde allt i PyScripter :).

Alla övningar handlade om att försöka följa TDD-stegen: Test - Code - Refactor enligt följande:
  1. Design (vad ska göras?)
  2. Skriv ett enhetstest 
  3. Kompilera (det ska inte gå att kompilera eftersom implementation inte är skriven)
  4. Implementera (endast) den kod som krävs för att koden ska kompilera
  5. Kör testet och se att det fallerar.
  6. Implementera (endast) den kod som krävs för att testet ska gå igenom
  7. Kör testet och se att det passerar.
  8. Refaktorera
  9. Repetera
Personligen anser jag att ovanstående steg är överdrivna. Dock fick jag lite mersmak för att designa testerna först och efterhand implementationen. Med facit i hand brukar jag skriva viss logik först, och avsluta med tester och repetera tills jag känner att det viktigaste är verifierat och testat. Antar att jag har lite kvar att lära för att fullt följa TDD :)

I nästa del tittade vi en hel del på patterns, anti-patterns och code smells. Vi tittade ganska mycket på mockning / verifiering, design och refaktorering. I slutet av kursen nämndes även integrationstestning, databastestning, automatiserad acceptanstestning, Javascript - testning, testning av GUI samt BDD (Behaviour Driven Development).

Sammanfattning
De agila delarna, XP, metodiken och principerna bakom TDD tilltalade mig. Anser också att jag fick mer kunskap inom ämnet. Koden i kursmaterialet var i många fall ganska förlegad (vilket jag förstår eftersom det är svårt att hålla detta uppdaterat). Eftersom kursen vände sig både till erfarna TDD-utvecklare och utvecklare som aldrig jobbat med TDD gick tyvärr en hel del tid åt på att förklara många grunder (som t.ex mockning och DI). Jag hade hellre sett att man delade upp denna kurs i en avancerad del och en nybörjardel och att man i den avancerade kursen tittat mer på t.ex. BDD, GUI och integrationstestning eftersom vi knappt hann gå igenom dessa ämnen.

Annars är jag nöjd! Läraren var mycket duktig, trevlig, hjälpsam och intresserad.
Kan rekommendera kursen till de som är intresserade av TDD och vill lära sig mer om ämnet.

/Nils

2 kommentarer:

  1. Bra artikel. Kul att du nämnde BDD.

    Jag har i nuvarande uppdrag arbetat mycket med att möjliggöra automatiserade rök tester. Tester som måste kunna skapas och underhållas utan att kalla på utvecklare.

    Fitnesse av Uncle Bob är det spår som vi valt att arbeta med.

    Det är inte BDD men det är ett steg på vägen. Fitnesse är ju egentligen byggt för att skapa Given When Then scenarios (BDD).

    Jag kan helt klart se syftet och styrkan med denna typ av tester (som ett komplement till TDD).

    Ruby / Cucumber verkar vara stort oxå. Skulle vara intressant att lära sig mer om det.

    SvaraRadera
  2. Hej Demas, såg inte svaret förrän nu. Tack!

    Kan också se BDD som ett komplement till TDD: Kursledaren nämnde dock att ett hinder med BDD (Given When Then) var att det i vissa fall var svårt för en kund/beställare (utan programmeringserfarenhet) att skriva dessa test-case, även om de slipper kalla på en utvecklare.

    Självklart kan de lära sig skriva bra test-case, men i de flesta fall har de nog inte tid att sätta sig ner och skriva dem.

    Hoppas på att få en inblick i Fitnesse inom en snar framtid!

    SvaraRadera