Endagskurs i TDD/C# någon..?

31 december 2008

images4Har via mailinglistor jag är med på fått reda på att det går en webbaserad endagskurs i TDD för C#-utvecklare. Det verkar vara grunderna och lite till: använda enhetstestverktyg, att skriva tester, refactoring, dependency injection.

Låter det intressant? Besök denna adress och skriv upp dig ..

Taggar: , , ,


Varför enhetstesta – anledning 3

26 december 2008

images7Inlägg i denna serie, ”Anledningar att enhetstesta”:

Anledning 1 – Dokumentationseffekten

Anledning 2 – Säkerhetsbälteseffekten

Anledning 3 – Designeffekten


Vi är framme vid anledning 3: renare kod Designeffekten.

Det kan vara svårt att föreställa sig hur stor skillnad det gör på arkitekturen hos ett mjukvarusystem att börja enhetstesta den, eller till och med skriva enhetstester ända ifrån början.

Den huvudsakliga anledningen till att det gör en sådan skillnad är följande:

Det är svårt att testa ”fulkod”.

Tänk på det en stund. Tänk på vad du anser att fulkod är.

Med fulkod menar jag globalt tillstånd (statiska variabler), beroendegalenskap t.ex. ”spindelklasser” (även kända som ”control freaks”) som känner och kontrollerar tjugo andra klasser (rotar kanske t.o.m. i deras interna data, gud förbjude..), kod i användargränssnittsklasser (logik i event handlers t.ex.), kod som är för nära knutet till databas- eller filsystemslagret av systemet, kod som gör logiska beräkningar nära eller i utritningslogik för grafik etc.

Du har säkert en känsla av vad som är ”fulkod” om du programmerat mer än på nybörjarnivå.

Att det är svårt att testa sådan kod kommer du att lära dig om du börjar enhetstesta ett befintligt system som är lite skräpigt. Jag kan avslöja att en anledning är att koden är så beroende av tredjeparts-API:er vilket gör att testkod måste interagera ganska intimt med biblioteket ifråga – något som tyvärr ofta är om inte omöjligt så åtminstone rejält besvärligt.

När du använder dig av Test-Driven Development (TDD) vänder du på steken: du skriver tester innan du skriver kod! Givetvis blir klasser som designas efter denna princip (tester först!) mycket lättare att testa. Då måste du skriva koden mer eller mindre fritt från inblandning av beroenden, eftersom beroenden är så svårtestade. Givetvis måste din kod fortfarande interagera med tredjeparts-APIer men du kommer att behöva skikta systemet och abstrahera bort detaljer som exakt vilket tredjeparts-API ditt program använder sig av.

Men det som är intressant, och samtidigt svårt att förstå, är att testbar kod per automagi blir renare, snyggare och mer ”pattern-inriktad”/”objektorienterad”/”pick-your-fancy-word”-aktig kod! Pröva själv med ett hemmaprojekt. Så får du se!

Tidigare avsnitt i denna serie:

Anledning 1 – Dokumentationseffekten

Anledning 2 – Säkerhetsbälteseffekten

Taggar: , , , , ,


Varför enhetstesta – anledning 2

20 december 2008

images7Inlägg i denna serie, ”Anledningar att enhetstesta”:

Anledning 1 – Dokumentationseffekten

Anledning 2 – Säkerhetsbälteseffekten

Anledning 3 – Designeffekten


Förra veckan skrev jag ”Varför enhetstesta – anledning 1”. Då tog jag upp den dokumenterande effekt enhetstestning har.

Denna vecka tänkte jag diskutera det säkerhetsbältesliknande system man får när man har enhetstester på sina klasser.images2

Har du varit med om följande? En av supportkillarna kommer fram till dig och säger

Det finns en bugg i kommando X. Om man öppnar den här filen, och kör kommandot på det här och det här sättet, kraschar den här dialogen när man klickar på denna knapp”.

Du är fullt uppe i implementationen av en helt annan del av systemet, men eftersom det är en viktig kund och kunden såklart vill ha det här fixat på stört så är det läge att byta uppgift på momangen.

Buggletandet börjar. Du börjar med att reproducera felet, och mycket riktigt kraschar dialogen när du trycker på knappen i given situation. Du börjar skumma dialogkod och rotar dig ned genom klasser och algoritmer med hjälp av debuggern.

Felet ligger i metod M av klass K som kraschar eftersom den försöker anropa en metod på ett objekt som av någon anledning är Null – Null exception krasch boom bang.

Så du ska in och göra ingrepp i en metod du skrev för ett halvår sedan. Du är inte helt på det klara med hur metoden fungerar – inte heller på de klasser som anropar denna klass.

Vi ska nu göra en studie av vad som utspelar sig i en sådan här situation med och utan enhetstest.

Fall 1: Inga enhetstester

images3 Du är inne i ”gammal kod” (legacy code är kod utan enhetstester enligt Michael Feathers – bibel i min hylla – klicka på bilden!) och vill helst städa upp för att förstå vad som egentligen händer (varför objektet är Null i denna metod); metoden som kraschar är ganska lång (>20 rader kod) och några variabelnamn är förvirrande vilket inte underlättar förståelsen. Och av tidigare erfarenhet vet du hur farligt det är att börja ändra om kod som ”nästan fungerar”. Kommandot funkar ju trots allt i de flesta situationer! Det är ju i just den här konstiga situationen buggen uppstår.

Vad göra? Ja helst vill du röra vid så lite kod som möjligt här i ”djungeln”. Du debuggar länge länge för att förstå vem som anropar vem, vilka relationer objekten har till varandra osv. Till sist fattar du att objektet som ger null-exception inte sätts i en klass K3, givet ovanstående situation, och det felet propageras ned ända till K1 och där blir det Null exception. Du lägger på en ytterligare if-sats i K3 för bryta exekveringen av aktuell metod i den situation kunden har hittat, eftersom det är på tok för mycket förändringar av kodstrukturen (och alla de risker för trasering av andra delar av kommandot det innebär!) för att motivera en korrekt buggfix. Nu undviks åtminstone programkraschen.

Istället hittar du ett alternativt sätt att utföra det kunden vill ha gjort, meddelar support hur och ber honom förklara för kunden. Buggen är så att säga ”fixad” – eller egentligen är den inte det. Det är bara för jobbigt och riskabelt att göra aktuell förenkling/generalisering av koden för att lösa det egentliga problemet. Support förstår och accepterar detta – även om han tycker att det varit bättre om ”det bara funkat”.

Fall 2: Du har enhetstester

Denna gång tittar du på kod som har säkerhetsbälte runt omkring sig.

images4Det betyder att om du förändrar koden, t.ex. för att minska längden på en metod, döpa om variabler och metoder hit och dit, stuva om saker och ting så det går att förstå vad som händer, så kan du köra enhetstesterna för att se om du ”pajat” något. För då skulle något eller flera enhetstester ge ”larm”. Klicka på bilden för att se en lista på enhetstestningsverktyg i massa olika programmeringsspråk.

Känner du skillnaden? Nu kan du strukturera om koden så att det går att förstå vad som händer. Ta bort den där konstiga statiska (globala!) variabeln och instansiera klassen på riktigt från dess anropare, helt enkelt därför att det förenklar koden. Visst behöver du kanske ändra ett eller annat test på vägen (en oftast tämligen enkel process då testkod är och bör vara enkel att läsa!) men du har en distinkt annan känsla i arbetsflödet – du känner dig långt mer säker i dina förändringar. När du förändrar saker behöver du knappt analysera på något djupare plan vad förändringen har för effekter, eftersom enhetstesterna kommer säga till om du förstör något!

Men jag ska inte överdriva hur bra detta system fungerar. Allt beror faktiskt på hur bra enhetstesterna är skrivna – om de inte täcker upp hela klasser så är det vanskligt att ändra i någon metod som inte är har enhetstest. Och även metoder som är täckta av enhetstest kan ha udda kodvägar genom logiken som inte testas av något testfall.

Det är egentligen som med säkerhetsbälte och bilarmed säkerhetsbälte på är du inte garanterad att överleva vid en olycka. Men du har bra mycket bättre odds än utan bälte!

images5

Taggar: , , , ,


Kontinuerlig testkörning i Visual Studio

16 december 2008

Häromdagen skrev jag om kontinuerlig testkörning i Python.

Det experimentet fungerade så bra att jag nu utvecklat en liknande lösning i Visual Studio, där jag kodar i C#.

Samma princip, bara det att jag trycker F6 (build solution, som även sparar icke-sparade filer) istället för Ctrl+S som jag gjorde i Python.

Så här ser det ut: Visual Studio autotesting experiment

  • Till vänster Visual Studio.
  • Uppe till höger ett litet dosfönster som kontinuerligt kör NUnit på aktuellt projekt (hårdkodat, jag ändrar bara i python-scriptet som körs när jag byter aktuellt projet).
  • Nere till höger använder jag OpenOffice som ett klotterplank för refactoring/förenklingsidéer och TODO-lista.

När man stängt av bygget av alla projekt som inte är under utveckling just nu mha Configuration manager går bygget hyffsat snabbt, och eftersom enhetstesterna körs i bakgrunden får jag ett nästan lika snabbt utvecklingstempo som i Python. IntelliSense ger ju en positiv inverkan på hastigheten också, men det känns ändå inte lika kvickt som Pythonmiljön.

Taggar: , , ,


Kontinuerlig testkörning i Python

15 december 2008

Satt och lekte med ett ”autotestscript” i helgen. Det är mycket simpelt uppbyggt:

  1. Kör Test.py som innehåller enhetstest för projektet
  2. Vänta i fem sekunder
  3. Upprepa från 1

Jag har ”snott” idén från en kommentar angående utvecklingen av Google Chrome – tydligen kör Google ibland enhetstesterna kontinuerligt i bakgrunden. På så sätt får man ett ”larm” när man bryter mot något enhetstest!

Rent konkret kör jag scriptet, som jag kallar autotests.py, i ett terminalfönster. Invid detta fönster har jag igång två editorfönster jag skriver kod i, dels enhetstesterna, dels projektkoden. Jag har alltså tre filer i projektet som utvecklas:

  • Main.py – själva programkoden
  • Tests.py – enhetstester för Main.py
  • autotest.py – kontinuerlig körning av Tests.py

Känslan hittills är go! Det blir ett mjukt men ändå högt tempo på kodandet. Lägga till ett test, se det larma, lägga till kod för att få testet att gå igenom. Detta utan att behöva kompilera, vänta på bygget + enhetstestkörning. Bara trycka Ctrl+S när jag skrivit nått nytt.

autotest.py ser ut så här:

import os, time
while True:
  for i in range(0,50):
    print ""
  os.system("python Tests.py")
  time.sleep(5)
autotest

Autotest in action

Taggar: , , , ,


Varför enhetstesta – anledning 1

13 december 2008

images7Inlägg i denna serie, ”Anledningar att enhetstesta”:

Anledning 1 – Dokumentationseffekten

Anledning 2 – Säkerhetsbälteseffekten

Anledning 3 – Designeffekten


Jag tänkte köra en serie inlägg med fokus på anledningar till att enhetstesta. Jag kommer att köra dem utan inbördes ordning – alltså utan någon sorts prioritering dem emellan. Delvis för att jag inte är 100% säker på vilket argument som är det viktigaste. Det finns, som jag upplever det, många anledningar! Gissar på ett inlägg i veckan, vi får se hur snabbt det går.

Så här kommer en anledningen idag; det blir den dokumenterande effekt enhetstester har.

Om man jobbar på ett projekt tillsammans med några kollegor, eller för den delen tittar på sin egen kod från några månader sedan, händer det att man tar sig för pannan och undrar ”Hur fungerar nu den här klassen/funktionen igen?”. Exempelvis kan jag ställa mig frågor i stil med dessa:

nintendo-lego-video-game-01

  • ”Kan jag skicka in null för parametern secondaryPoly eller klarar inte funktionen av det? Måste jag skapa en tom poly då i min situation?”
  • ”Är det meningen att jag ska lägga till alla A-punkter först och sedan alla B-punkter i gridden? Kan jag blanda?”
  • ”Kan jag använda Compute() och sedan lägga till några A:n, för att sedan kunna använda Compute() igen?”
  • ”Kommer metoden att krascha om jag skickar in ett negativt tal, eller bara returnera 0?”

Den typen av frågor – om hur det är tänkt att en funktion eller ett objekt ska fungera – tillhör egentligen dess dokumentation. Men som van programmerare vet jag väldigt väl hur sällan dokumentation kommer på plats – och hur lite jag litar på dokumentation som finns (den blir så snabbt gammal pga ändringar i kodbasen!). Ofta är inte heller dokumentationen heltäckande (så kallade edge cases beskrivs inte, utan bara huvudfunktionen).

Utan enhetstester, finns det två vägar – störa sin kollega (om det inte är min egen kod!) – eller läsa igenom hur koden fungerar genom att titta under huven – läsa källkoden direkt. Och om man frågar en kollega är det inte alltid man får 100% pålitliga svar – de har inte allt i huvudet, lika lite som jag.

Med enhetstester finns det en tredje väg. Du kan kolla direkt på hur koden är tänkt att fungera genom att läsa igenom enhetstesterna!

För att få lite smak för detta argument, jämför hur du förstår klassen ”Countdown” genom att både läsa dess kod och dess enhetstester. Observera att detta är en väldigt liten klass – och med en större klass blir den dokumenterande effekten än viktigare såklart!

Countdown.cs (klasskod)

CountdownFixture.cs (enhetstester)

Taggar: , , , ,


Google Mock

12 december 2008

images4Har just fått nys om ett nytt mocking framework för C++, utvecklat av Google. Det är BSD license så det är helt öppet. Läs mer här.

Not: Mocking är att skapa ”fakeinstanser” av objekt i objektorienterade språk – det underlättar vid enhetstester där flera objekt är inblandade.  En mock påminner lite om en papegoja på så sätt att den upprepar vad du sagt till den! Du kan läsa vad jag skrivit om mocking här.

Taggar: , , , ,


Fattigmannens enhetstestning

08 december 2008

imagesJag är vän av enhetstestning. Speciellt den variant som innebär att man skriver tester före kod. Den tekniken är känd som TDD – Test Driven Development. Du kan läsa alla mina postningar om TDD genom att klicka här.

Men i denna postning tänkte jag introducera en enkel idé för att komma igång med enhetstestning, utan att behöva några speciella verktyg. Det finns nämligen en hel uppsjö med enhetsverktyg för de populära språken – ofta flera konkurrerande verktyg till varje språk! För att slippa bry sig/lära sig/installera och sätta sig in i något enskilt enhetstestverktyg tänkte jag berätta hur jag gör när jag stöter på någon ”främmande miljö” jag behöver skriva enhetstester i, där jag inte orkar sätta mig in i ytterligare ett enhetstestningsverktyg.

Idén är att ASSERT() är det enda vi behöver för att göra enhetstester!

Och assert() finns i så gått som alla miljöer man stöter på – kanske med namn som assume eller något liknande. Och om det inte finns, kan man lätt skriva en egen assert-metod. T.ex. mha exceptions- exit() eller halt-instruktioner, beroende på vilket programmeringsspråk/scriptspråk man nu befinner sig i.

Jag håller mig till programmeringsspråket C i denna postning, då jag tror att det är det mest spridda språket och därför är lätt för många att ”översätta” till sitt favoritspråk.

Låt säga att vi vill utveckla en metod som kontrollerar om en textsträng innehåller en annan textsträng. Vi kallar metoden ”Contains()” och den har följande signatur:

int Contains(char* source, char* substring);

Vi vill alltså att Contains(”abc”, ”ab”) ska bli ”-1” medan Contains(”abc”, ”g”) ska bli ”0”.

Här är skelettet till ett enkelt C-program:

#include <assert.h>

int main() {
  MainLoop();
}

int MainLoop() {
}

Jag har lagt till ”include <assert.h>” för att få med assert()-funktionen i programmet. Nu lägger vi till en metod ”RunSelfTests()” som är till för att köra de automatiska enhetstesterna när programmet körs:

#include <assert.h>

int main() {
  RunSelfTests();
  // Alla självtester gick igenom, kör huvudprogrammet!
  MainLoop();
}

int MainLoop() {
}

int RunSelfTests() {
}
// Kompileringsrad:
// gcc -o test1 test1.c

Ovanstående kodskelett är allt vi behöver för att komma igång med enhetstestning i C!

Nu kommer vi till själva kärnan i detta inlägg: enhetstesterna.

Ett enhetstest testar en ”punkt” i beteendet hos en ”enhet”/”aspekt” av programvaran. Rent konkret kontrollerar man att resultatet (utdata) av viss speciell indata är korrekt.

Man väljer gärna enkla fall, så att enhetstestet blir läsbart, lätt att skriva och förstå.

I vårat fall listar vi helt enkelt upp några specialfall och det förväntade resultatet av våran funktion Contains():

  • Contains(”abc”, ”abc”) ska vara sant
  • Contains(”abc”, ”def”) ska vara falskt
  • Contains(”olof”, ”of”) ska vara sant

.. och så vidare tills vi känner att vi kan ”lita” på våran funktion Contains() till 90% (100% är dumt att sikta på! Buggar finns alltid kvar i alla program, det lär man sig som programmerare med åren..)

#include <assert.h>

int Contains(char* source, char* substring) { return 0; }

int main() {
  // Kör själv-tester (asserts) först av allt.
  RunSelfTests();

  // Alla självtester gick igenom, kör huvudprogrammet!
  MainLoop();
}

int MainLoop() {
  // ej implementerad ännu..
}

int RunSelfTests() {
  assert(Contains("abc", "abc"));
  assert(Contains("abc", "bc"));
  assert(Contains("olof", "of"));
  assert(Contains("abc", ""));
  assert(!Contains("bc", "abc"));
}

Vi kan testa att kompilera & köra vårat program med ”gcc -o test1 test1.c” eller om du kör Visual Studio i Windows någon F-knapp. När vi kör programmet får vi ett felmeddelandet i stil med ”Assertion failed ‘Contains(”abc”, ”abc”) at row XX”.

Vi implementerar nu ”Contains” med hjälp av string.h-funktionen ”strstr” som fungerar ungefär som Contains faktiskt, men inte riktigt:

#include <assert.h>
#include <string.h>

int Contains(char* source, char* substring) {
  char* pos = strstr(source, substring);
  if(pos == 0)
    return 0;
  else
    return -1;
}

int main() {
  // Kör själv-tester (asserts) först av allt.
  RunSelfTests();
  // Alla självtester gick igenom, kör huvudprogrammet!
  MainLoop();
}

int MainLoop() {
 // ej implementerad ännu..
}

int RunSelfTests() {
  assert(Contains("abc", "abc"));
  assert(Contains("abc", "bc"));
  assert(Contains("olof", "of"));
  assert(Contains("abc", ""));
  assert(!Contains("bc", "abc"));
}

Nu ska våran kod kompilera och köra utan att någon assert() kraschar!

Taggar: , ,


Två programmeringsböcker beställda

05 december 2008

Beställde två böcker om programmering igår:

1. Kent Becks moderna klassiker ”Test Driven Development: By example”

2. Mike Cohns ”User stories applied”

9780321146533

Becks bok är något varje TDD-nörd (som jag är!) bör ha i sin hylla har jag förstått på mailinglistor jag är med på. Jag anser mig redan kunna en hel del om TDD (hållit på med det i snart två år) men det finns alltid mer att lära och Beck är en av förgrundsgestalterna ”i branschen”. TDD handlar om kvalité och mjukvarudesign hand i hand – något jag brinner för.

9780321205681User Stories Applied är lite mer av en joker jag hört gott om när det handlar mer om metodik/specifikation/kommunikation. Det är lite mer nära BDD – Behaviour Driven Development – en modernare variant av TDD som hittills gäckat min förståelse. Hoppas bygga upp mer fingertoppskänsla för BDD med hjälp av denna bok.

Trevlig julläsning förväntas i alla fall!

Taggar: , ,


Varför är testning av GUI/användarinteraktion så svårt?

18 september 2008

Jag håller på att utveckla ett kommando på jobbet. Det interagerar med användaren för att ta reda på vad det ska göra. Ungefär som en Wizard i Windows.

Jag brukar inte testa interaktion, bränd av erfarenhet från tidigare försök att testa användarinteraktion med hjälp av TDD.

Det är relativt lätt att förstå att det är svårt att testa Windows.Forms-GUIn; man måste instansiera och arbeta med Forms och Controls på ett sätt som man inte alls brukar göra. Dessutom tillkommer events-triggering som kan vara väldigt svårt att åstadkomma alls.

Men i mitt fall är det mer grundläggande: APIt jag använder är inte så komplicerat. Men det är fortfarande så j-a svårt att testa interaktion!

Därför frågar jag mig: vad är det som gör just interaktion så svårtestad..?

Interaktion består av ett slags dialog: fråga-svara-fråga-svara. Nästkommande fråga beror ofta på svaret som ges i föregående fråga. I mitt fall är det ”kommandot” som frågar ”användaren”, generellt är det alltså användaren som ger svaren och datorn som ställer frågorna.

Förutom att frågor kan bero på tidigare svar, kan sidoeffekter eller miljön som frågorna ställs i ändra på frågorna! T.ex. kan en fråga om ”välj skärprogram” undvikas helt om det bara finns precis ett skärprogram på ritningen. Frågan beror alltså också på miljön den ställs i, inte bara användarens svar.

Det är en massa beroenden här.

Kan det vara härifrån svårigheten kommer i interaktionstestning..? Beroendena? T.ex. beroende på filsystem eller databas vet man ju är komplicerat att testa. Beroenden rent generellt är ett bekymmer för testning. 

Och viktigare: kan vi attackera svårigheten på samma sätt som vi attackerar beroendesvårigheten när det gäller andra ”externals” (filer/db)? Dvs. dependency injection?

Jag gör ett försök:

    public interface IContext
    {
      Point2d PickZeroPoint();
      EntityCutProgram PickCutOrder();
      List<EntityCutProgram> PickECPsToAdd();
      void AddECPToCutOrder(ECP ecp, ECP cutorder);
    }

Det ger en väldigt enkel kod för själva kommandoexekveringen, [icke-komplett] exempel:

</pre>
public class MyCommand : Command {
    public void Execute(IContext context)
    {
      var cutorder = context.PickCutOrder();
      if (cutorder == null)
        context.PickZeroPoint();
      var ecps = context.PickECPsToAdd();
      if (ecps != null)
        foreach (var ecp in ecps)
          context.AddECPToCutOrder(cutorder, ecp);
    }
}

Nu kan vi bygga ett mock-object för IContext. Men innan vi gör det vill ja gå till hur man skriver ett test.

Vad är ett typiskt acceptanstest för en sådant här kommando..?

Ja det allra enklaste scenariot är att användaren startar kommandot, men ångrar sig och trycker Esc. Då ska kommandot avbrytas utan att något ändras i dess miljö.

Skriver först ned den mest naturliga översättningen av detta till kod:

</pre>
[Test]
public void Example1() {
  Given();
  User_runs_command();
  User_presses_Esc();
  Should_abort_command();
  Should_not_modify_context();
}

Hur ska vi realisera dessa metoder? Vi behöver

  • Något sätt att ”simulera” användaren
  • Något sätt att ”simulera” svar från miljön
  • Något sätt att få reda på om dialogen ”fallerar” någonstans (och var!)

Det sista är såklart viktigt för avbuggningstempot.

Infall: det känns som om jag helt enkelt vill skriva ned förväntad dialog och sedan verifiera den. Nytt försök på Exempel ett:

</pre>
[Test]
public void Dialog1() {
  Dialog_begins();
  Command_asks_PickCutOrder_Or_Create_new();
  User_presses_Esc();
  Dialog_ends();
}

Enklare, kortare, lättare att förstå(?). 

Hur realisera?

Det kan vara bra att komma ihåg läxan med att ”man behöver inte testa allt exakt”. Det kan räcka med ”grova drag” för att höja förtroendet för koden. 

Men för att kunna implementera User_presses_Esc() krävs det att jag kan ”rigga” returvärden från metoder. I detta fall räcker det med returvärde för ETT anrop.

Command_asks_PickCutOrder_Or_Create_new() kan realiseras mha anropslista.

</pre>
class ContextMock : IContext {
  List<string> expected;
  List<string> actual;

  public void Expect_PickCutOrder() { expected.Add("PickCutOrder"); }
  public ECP PickCutOrder() { callList.Add("PickCutOrder"); return new ECP(); }
  ...
  public void Verify() {
    CollectionAssert.AreEqual(expected, actual);
  }
}

To be continued…

Taggar: , ,


%d bloggare gillar detta: