Nybörjarens guide till testdriven utveckling

Testa din kod är irriterande, men effekten av att inte göra det kan vara storleksordningar mer irriterande! I den här artikeln använder vi testdriven utveckling för att skriva och testa vår kod mer effektivt.


Vad är testdriven utveckling?

Sedan datortiden började, har programmerare och buggar kämpat för överlägsenhet. Det är en oundviklig händelse. Även de största programmerarna faller byte mot dessa anomalier. Ingen kod är säker. Det är därför som vi testar. Programmerare, åtminstone sinniga, testa deras kod genom att köra den på utvecklingsmaskiner för att se till att det gör vad den ska.


Sane programmerare som testar sina program.
Bild med tillstånd av http://www.youthedesigner.com
Vansinnig programmerare som inte testar sina program.
Bild med tillstånd av http://www.internetannoyanceday.com

Testdriven utveckling är en programmeringsteknik som kräver att du skriver samma kod och automatisk testkod samtidigt. Detta säkerställer att du testar din kod - och gör att du kan retestera din kod snabbt och enkelt, eftersom det är automatiserat.

Hur fungerar det?

Testdriven utveckling, eller TDD som vi kallar det från och med nu, kretsar kring en kort iterativ utvecklingscykel som går något liknande:

  1. Innan du skriver någon kod måste du först skriva ett automatiskt test för din kod. När du skriver de automatiska testen måste du ta hänsyn till alla möjliga ingångar, fel och utgångar. På det här sättet är ditt sinne inte grumligt av någon kod som redan har skrivits.
  2. Första gången du kör ditt automatiska test ska testet misslyckas, vilket indikerar att koden inte är klar.
  3. Därefter kan du börja programmera. Eftersom det redan finns ett automatiserat test, så länge koden misslyckas, betyder det att det fortfarande inte är klart. Koden kan fixas tills den övergår alla påståenden.
  4. När koden passerar testet kan du sedan börja rengöra den, via refactoring. Så länge koden fortfarande passerar testet, betyder det att det fortfarande fungerar. Du behöver inte längre oroa sig för förändringar som introducerar nya buggar.
  5. Starta det hela igen med någon annan metod eller ett program.

Den testdrivna utvecklingscykeln
Bild med tillstånd av http://en.wikipedia.org/wiki/Test-driven_development

Bra, men hur är det bättre än vanlig testning?

Har du någonsin försiktigt hoppat över att testa ett program eftersom:

  • Du kände att det var slöseri med tid att testa, eftersom det bara var en liten kodändring?
  • Du kände lat att testa allt igen?
  • Du hade inte tillräckligt med tid att testa eftersom projektledaren ville att den skulle flytta upp till produktion ASAP?
  • Du sa till dig själv att du skulle göra det "imorgon"?
  • Du var tvungen att välja mellan manuell provning eller titta på senaste avsnittet av din favorit TV-show (Big Bang Theory)?

För det mesta sker inget, och du flyttar din kod till produktion utan problem. Men ibland, efter att du har flyttat till produktion, går allt fel. Du sitter fast och fixar hundra hål i ett sjunkande fartyg, med mer som visas varje minut. Du gör inte vill hitta dig själv i den här situationen.


Skruva den, flytta den bara till produktion!
Bild med tillstånd av http://phenomenaonbreak.wordpress.com

TDD var tänkt att eliminera våra ursäkter. När ett program har utvecklats med hjälp av TDD, tillåter det oss att göra förändringar och test snabbt och effektivt. Allt vi behöver göra är att köra automatiserade tester, och voila! Om det passerar alla automatiska tester, då är det bra att gå - om inte, så betyder det bara att vi bröt något med förändringarna. Genom att veta vilka exakta delar av testet som misslyckades, gör det också oss lätt att identifiera vilken del av förändringarna den bröt, så det gör det lättare att fixa buggarna.


Jag är såld Hur gör vi det här?

Det finns en mängd PHP-automatiserade testramar där ute som vi kan använda. En av de mest använda testramarna är PHPUnit.

PHPUnit är ett bra testramverk som enkelt kan integreras i dina egna projekt eller andra projekt som byggs utöver populära PHP-ramar.

För våra ändamål men vi behöver inte de många funktioner som PHPUnit erbjuder. Istället väljer vi att skapa våra test med ett mycket enklare testramverk, kallat SimpleTest.

I nästa steg antar vi att vi utvecklar en gästboksapplikation där någon användare kan lägga till och visa gästbokposter. Låt oss anta att markeringen har slutförts, och att vi helt enkelt gör en klass som innehåller applikationslogik i gästboken, vilket är var applikationen sätter in och läser till databasen. Läsningsdelen av den här klassen är vad vi ska utveckla och testa.


Steg 1. Ställ in SimpleTest

Detta är förmodligen det enklaste steget av alla. Även den här killen kan göra det:


Jag kan göra det här? Jag kan använda, min, um? hjärna!
Bild med tillstånd av http://longstreet.typepad.com/

Ladda ner SimpleTest här och extrahera till en mapp efter eget val - helst mappen där du ska utveckla din kod, eller din PHP include_path för enkel åtkomst.

För den här handledningen har jag satt upp mappen så här:

Index.php kör guestbook.php och anropa visningsmetoden och visa inmatningarna. Inne i klassens mapp är där vi lägger guestbook.php-klassen, och testmappen är där vi placerar det enklaste biblioteket.


Steg 2. Planera ditt angrepp


Bild med tillstånd av http://connections.smsd.org/veterans

Det andra steget, som faktiskt är det viktigaste, är att börja skapa dina test. För detta behöver du verkligen planera och tänka på vad din funktion kommer att göra, vilka möjliga ingångar det kommer att få och motsvarande utdata som den ska skicka. Detta steg liknar att spela ett schackspel - du behöver veta allt om din motståndare (programmet), inklusive alla hans svagheter (möjliga fel) och styrkor (vad händer om det går framgångsrikt).

Så för vår gästbok ansökan, låt oss lägga ner schematerna:

Se

  • Den här funktionen kommer inte att ha några inmatningar eftersom det bara kommer att hämta alla poster från databasen och skicka tillbaka de data som ska skrivas ut.
  • Det kommer att returnera en rad gästboksposter, med namnet på affischen och hans meddelande. Om det inte finns några poster, borde det fortfarande returnera en tom matris.
  • Om det finns poster, kommer matrisen att ha 1 eller flera värden i den.
  • Samtidigt har matrisen en specifik struktur, något liknande:
 Array (['name'] = "Bob" ['message'] = "Hej, jag är Bob.") [1] => Array ['message'] = "Hej, jag är Tom."))

Steg 3. Skriv ett test!


Bild med tillstånd av http://cflhomeless.wordpress.com

Nu kan vi skriva vårt första test. Låt oss börja med att skapa en fil som heter guestbook_test.php inuti testmappen.

  

Låt oss då konvertera vad vi har bestämt från steg två,.

 lägg till ("Bob", "Hej, jag är Bob."); $ gästbok-> lägg till ("Tom", "Hej, jag är Tom."); $ entries = $ gästbok-> visaAll (); $ count_is_greater_than_zero = (räkna ($ poster)> 0); $ This-> assertTrue ($ count_is_greater_than_zero); $ this-> assertIsA ($ entries, 'array'); foreach ($ poster som $ entry) $ this-> assertIsA ($ entry, 'array'); $ This-> assertTrue (isset ($ inträde [ 'name'])); $ This-> assertTrue (isset ($ inträde [ 'meddelande']));  funktion testViewGuestbookWithNoEntries () $ guestbook = ny gästbok (); $ Guestbook-> TaBortAlla (); // Ta bort alla poster först så vi vet att det är en tom tabell $ entries = $ gästbok-> visaAll (); $ this-> assertEqual ($ entries, array ()); 

Påståenden försäkra sig om att en viss sak är vad den ska vara - i grunden säkerställer den att det som returneras är vad du förväntar dig att återvända. Till exempel, om en funktion är tänkt att återvända sant om det lyckas, då i vårt test, borde vi hävda att returvärdet är lika med sant.

Som du kan se här, testa vi visning av gästboken med poster och utan. Vi kontrollerar om dessa två scenarier passerar våra kriterier från steg två. Du har säkert märkt att alla våra testfunktioner börjar med ordet "test". Det gjorde vi för, när SimpleTest kör denna klass, kommer den att leta efter alla funktioner som börjar med ordet "test" och köra det.

I vår testklass har vi också använt vissa påståenden, såsom assertTrue, assertIsA och assertEquals. AssertTrue-funktionen kontrollerar huruvida ett värde är sant. AssertIsA kontrollerar om en variabel är av en viss typ eller klass. Och slutligen kontrollerar assertEquals om en variabel är helt lika med ett visst värde.

Det finns andra påståenden som tillhandahålls av SimpleTest, som är:

assertTrue ($ x) Misslyckas om $ x är falskt
assertFalse ($ x) Misslyckas om $ x är sant
assertNull ($ x) Misslyckas om $ x är inställd
assertNotNull ($ x) Misslyckas om $ x inte anges
assertIsA ($ x, $ t) Misslyckas om $ x inte är klassen eller skriv $ t
assertNotA ($ x, $ t) Misslyckas om $ x är av klassen eller skriv $ t
assertEqual ($ x, $ y) Misslyckas om $ x == $ y är falskt
assertNotEqual ($ x, $ y) Misslyckas om $ x == $ y är sant
assertWithinMargin ($ x, $ y, $ m) Misslyckas om abs ($ x - $ y) < $m is false
assertOutsideMargin ($ x, $ y, $ m) Misslyckas om abs ($ x - $ y) < $m is true
assertIdentical ($ x, $ y) Misslyckas om $ x == $ y är falsk eller en typmatchning
assertNotIdentical ($ x, $ y) Misslyckas om $ x == $ y är sant och typer matchar
assertReference ($ x, $ y) Misslyckas, såvida inte $ x och $ y är samma variabel
assertClone ($ x, $ y) Misslyckas, såvida inte $ x och $ y är identiska kopior
assertPattern ($ p, $ x) Misslyckas om inte regex $ p matchar $ x
assertNoPattern ($ p, $ x) Misslyckas om regex $ p matchar $ x
expectError ($ x) Svalar något kommande matchningsfel
hävda ($ e) Misslyckas på misslyckat förväntat objekt $ e

Uppgiftsmetodlista med tillstånd av http://www.simpletest.org/en/unit_test_documentation.html


Steg 4. Misslyckas med att vinna


Bild med tillstånd av http://verydemotivational.com

När du är klar med att skriva koden ska du köra testet. Första gången du kör testet, det Borde misslyckas. Om det inte gör det betyder det att ditt test inte testar någonting.

För att köra ditt test, kör helt enkelt guestbook_test.php i din webbläsare. Du bör se detta först:

Detta hände eftersom vi ännu inte har skapat vår gästboksklass. För att göra så skapar du gästboken.php i din klasskatalog. Klassen ska innehålla de metoder vi planerar att använda, men bör inte innehålla någonting än i början. Kom ihåg att vi skriver först testerna innan skriver någon kod.

  

När du kör testet igen ska det se ut så här:

Som vi kan se här, vinner vårt test nu genom att misslyckas. Det betyder att vårt test nu är klart för att få "svarat".


Bild med tillstånd av http://www.gamercastnetwork.com/forums

Steg 5. Besvara ditt test med skrivkoden


Vid något tillfälle har vi alla känt så här när vi programmerar.
Bild med tillstånd av http://fermentation.typepad.com/fermentation

Nu när vi har ett fungerande automatiserat test kan vi börja skriva kod. Öppna din guestbook.php klass och börja skapa svaret på ditt test.

  'Kirk', 'message' => 'Hej, jag är Kirk.' ), array ('name' => 'Ted', 'message' => 'Hej, jag är Ted.')); public function viewAll () // Här ska vi hämta alla poster från databasen. // Detta simuleras genom att returnera $ _entries array Return Return self: $ _ entries;  public function add ($ namn, $ message) // Här simulerar vi infogning i databasen genom att lägga till en ny post i $ _entries array // Detta är rätt sätt att göra det: själv :: $ _ poster [] = array ('name' => $ namn, 'message' => $ meddelande); själv :: $ _ poster [] = array ('notname' => $ namn, 'notmessage' => $ meddelande); // oops, det finns en bugg här någonstans återvänd sant;  public function deleteAll () // Vi har bara satt $ _entries array för att simulera själv :: $ _ entries = array (); återvänd sant; 

Denna gästbok.php-klass har några fel i det med syfte, så vi kan se hur det ser ut om vårt test misslyckas.

När vi kör vårt test bör vi se något så här:

Testutgången visar oss i vilket test och i vilket påstående vår kod misslyckades. Härav kan vi enkelt fastställa att linje 16 och 17 var påståendet som kastade felet.

 assertTrue (isset ($ posten [ 'name'])); $ This-> assertTrue (isset ($ posten [ 'meddelande'])) ;? 

Det här förklarar tydligt att den returnerade postrutan inte hade rätt matrisnyckel. Baserat på detta vet vi lätt vilken del av vår kod som gick fel.

  $ namn, "meddelande" => $ meddelande); //fast! återvänd sant; ? 

Nu när vi kör vårt test igen bör det visa oss:


Steg 6. Refactor och förfina din kod


Bilder med tillstånd av http://www.osborneink.com och http://phuketnews.phuketindex.com

Eftersom koden vi testar här är ganska enkel har vår testning och buggfixering inte varit så lång. Men om det här var en mer komplex applikation, skulle du behöva göra flera ändringar i din kod, göra den renare så att det är lättare att underhålla, och många andra saker. Problemet med detta är dock att förändringen vanligtvis introducerar ytterligare fel. Det här är vårt automatiserade test som kommer in - när vi gör ändringar, kan vi helt enkelt springa provet igen. Om det fortfarande passerar betyder det att vi inte bryter något. Om det misslyckas vet vi att vi gjorde ett misstag. Den informerar oss också om var problemet är och förhoppningsvis hur vi kan fixa det.


Steg 7. Skölj och repetera


Bild med tillstånd av http://www.philstockworld.com

Så småningom, när ditt program kräver ny funktionalitet måste du skriva nya test. Det är lätt! Skölj och upprepa procedurerna från steg två (eftersom dina SimpleTest-filer redan ska ställas in) och starta cykeln igen.


Slutsats

Det finns mycket mer djupgående testdrivna utvecklingsartiklar där ute, och ännu mer funktionalitet än SimpleTest än vad som visades i denna artikel-saker som mocka objekt, stubbar, vilket gör det enklare att skapa test. Om du vill läsa mer, bör Wikipedias testdrivna utvecklingssida sätta dig på rätt väg. Om du är angelägen om att använda SimpleTest som ditt testramverk, bläddra i dokumentationen online och var noga med att granska dess andra funktioner.

Testning är en integrerad del av utvecklingscykeln, men det är för ofta det första som ska skäras när deadlines är nära förestående. Förhoppningsvis kommer du, efter att ha läst den här artikeln, uppskattat hur bra det är att investera i testdriven utveckling.

Vad är dina tankar om testdriven utveckling? Är det något du är intresserad av att genomföra, eller tycker du att det är slöseri med tid? Låt mig veta i kommentarerna!