Fattigmannens enhetstestning

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: , ,

2 kommentarer till Fattigmannens enhetstestning

  1. akeinexile skriver:

    Intressant introduktion måste jag säga, du kanske kan lägga till någon länk till en sammanfattning av de funktioner som finns tillgängliga via assert.h?

    Brukar du ta bort asserts när du släpper en skarp version av ett program eller låter du dom ligga kvar? Hur pass väl fungerar det att använda asserts i flertrådiga processer? Jag kan tänka mig att man lätt kan introducera fel ifall man försöker kontrollera saker som bygger på kommunikation mellan trådar (genom att glömma lås och liknande strukturer på relevanta ställen).

  2. Olof Bjarnason skriver:

    Tack akeinexile!

    Ärligt talat vet jag inte exakt vilka fler funktioner som finns i assert.h — assert själv är den enda man egentligen behöver. Det är lite ”rått” men det ger mycket ”bang for the buck” så att säga. Om man vill ha bättre verktyg tycker jag man ska kolla på CppUnitLite t.ex. för C/C++, NUnit för .NET eller JUnit för Java. Python har doctest som jag gillar (enkelt och litet) och Ruby RSpec. Men det finns som sagt massor av verktyg, det är bara att gå med på en mailinglista för språket ifråga och fråga folk vad de använder.

    När det gäller att släppa programvaran: ett trick är att göra så här:

    int RunSelfTests()
    #ifdef DEBUG
    // asserts…
    #endif
    }

    Då kommer inte testerna att köras i ”skarpt läge” och inte heller ta upp någon plats i binärfilen.

Kommentera

Fyll i dina uppgifter nedan eller klicka på en ikon för att logga in:

WordPress.com Logo

Du kommenterar med ditt WordPress.com-konto. Logga ut / Ändra )

Twitter-bild

Du kommenterar med ditt Twitter-konto. Logga ut / Ändra )

Facebook-foto

Du kommenterar med ditt Facebook-konto. Logga ut / Ändra )

Google+ photo

Du kommenterar med ditt Google+-konto. Logga ut / Ändra )

Ansluter till %s

%d bloggare gillar detta: