The Newbie's Guide to Test-Driven Development

Het testen van je code is vervelend, maar de impact van het niet doen van deze code kan orden van grootte hinderlijker zijn! In dit artikel gebruiken we testgestuurde ontwikkeling om onze code effectiever te schrijven en te testen.


Wat is testgestuurde ontwikkeling?

Sinds het begin van het computertijdperk hebben programmeurs en bugs gestreden voor suprematie. Het is een onvermijdelijke gebeurtenis. Zelfs de grootste programmeurs vallen ten prooi aan deze anomalieën. Geen code is veilig. Dat is waarom we testen. Programmeurs, in ieder geval verstandige, testen hun code door deze op ontwikkelmachines uit te voeren om er zeker van te zijn dat het doet wat het zou moeten doen.


Zuivere programmeur die zijn programma's test.
Afbeelding met dank aan http://www.youthedesigner.com
Insane programmeur die zijn programma's niet test.
Afbeelding met dank aan http://www.internetannoyanceday.com

Test gedreven ontwikkeling is een programmeertechniek waarbij u tegelijkertijd code en geautomatiseerde testcode moet schrijven. Dit zorgt ervoor dat u uw code test - en stelt u in staat om uw code snel en gemakkelijk opnieuw te testen, omdat deze automatisch is.

Hoe werkt het?

Testgestuurde ontwikkeling, of TDD zoals we dat vanaf nu noemen, draait om een ​​korte iteratieve ontwikkelingscyclus die ongeveer als volgt verloopt:

  1. Voordat u een code schrijft, moet u eerst een geautomatiseerde test voor uw code schrijven. Tijdens het schrijven van de geautomatiseerde tests moet u rekening houden met alle mogelijke invoer, fouten en uitvoer. Op deze manier wordt je geest niet vertroebeld door een code die al is geschreven.
  2. De eerste keer dat u uw geautomatiseerde test uitvoert, moet de test mislukken. Dit geeft aan dat de code nog niet gereed is.
  3. Daarna kunt u beginnen met programmeren. Aangezien er al een geautomatiseerde test is, betekent dit dat de code nog steeds niet gereed is, zolang de code niet slaagt. De code kan worden gecorrigeerd totdat deze alle beweringen heeft doorstaan.
  4. Nadat de code de test heeft doorstaan, kun je beginnen met het opruimen, via refactoring. Zolang de code nog steeds de test doorstaat, betekent dit dat deze nog steeds werkt. U hoeft zich niet langer zorgen te maken over wijzigingen die nieuwe bugs introduceren.
  5. Begin het geheel opnieuw met een andere methode of programma.

De testgestuurde ontwikkelingscyclus
Afbeelding met dank aan http://en.wikipedia.org/wiki/Test-driven_development

Geweldig, maar hoe is dit beter dan regulier testen?

Heb je ooit een programma met opzet overgeslagen omdat:

  • Je vond dat het een verspilling van tijd was om te testen, omdat het slechts een kleine codewijziging was?
  • Je voelde je lui om alles opnieuw te testen?
  • Je had niet genoeg tijd om te testen omdat de projectmanager wilde dat het zo snel mogelijk naar de productie ging?
  • Je zei tegen jezelf dat je het "morgen" zou doen?
  • Je moest kiezen tussen handmatige tests of het bekijken van de nieuwste aflevering van je favoriete tv-programma (Big Bang Theory)?

Meestal gebeurt er niets en kunt u uw code probleemloos naar productie verplaatsen. Maar soms, als je eenmaal bent verhuisd naar de productie, gaat het mis. Je bent vast aan het repareren van honderd gaten in een zinkend schip, met meer elke minuut. Je doet niet wil je jezelf in deze situatie bevinden.


Schroef het, verplaats het gewoon naar productie!
Afbeelding met dank aan http://phenomenaonbreak.wordpress.com

TDD was bedoeld om onze excuses te elimineren. Wanneer een programma is ontwikkeld met behulp van TDD, kunnen we wijzigingen aanbrengen en dit snel en efficiënt testen. Het enige wat we moeten doen is de geautomatiseerde tests uitvoeren en voila! Als het alle geautomatiseerde tests doorstaat, dan zijn we goed om te gaan - zo niet, dan betekent dit gewoon dat we iets hebben gebroken met de veranderingen. Door te weten welke exacte delen van de test zijn mislukt, kunnen we ook gemakkelijk vaststellen welk deel van de wijzigingen is verbroken, zodat het eenvoudiger wordt om de bugs te verhelpen.


Ik ben verkocht. Hoe doen we dit?

Er is een groot aantal PHP-geautomatiseerde testraamwerken die we kunnen gebruiken. Een van de meest gebruikte testkaders is PHPUnit.

PHPUnit is een geweldig testraamwerk dat eenvoudig kan worden geïntegreerd in uw eigen projecten of andere projecten die bovenop populaire PHP-frameworks worden gebouwd.

Voor onze doeleinden hebben we echter niet de vele functies nodig die PHPUnit biedt. In plaats daarvan kiezen we ervoor om onze tests te maken met behulp van een veel eenvoudiger testraamwerk, genaamd SimpleTest.

Laten we in de volgende stappen aannemen dat we een gastenboektoepassing ontwikkelen waarin elke gebruiker gastenboekitems kan toevoegen en bekijken. Laten we aannemen dat de markup is voltooid en dat we gewoon een klasse maken die de applicatielogica van het gastenboek, waar de applicatie wordt ingevoegd en gelezen naar de database. Het leesgedeelte van deze klas is wat we gaan ontwikkelen en testen.


Stap 1. Stel SimpleTest in

Dit is misschien wel de gemakkelijkste stap van allemaal. Zelfs deze man zou het kunnen doen:


Ik kan dit doen? Ik kan gebruiken, mijn ...? hersenen!
Afbeelding met dank aan http://longstreet.typepad.com/

Download SimpleTest hier en pak het uit naar een map naar keuze - bij voorkeur de map waar u uw code gaat ontwikkelen, of uw PHP include_path voor eenvoudige toegang.

Voor deze zelfstudie heb ik de map zo ingesteld:

Index.php voert guestbook.php uit, roept de weergavemethode aan en geeft de vermeldingen weer. In de klassenmap plaatsen we de guestbook.php-klasse en in de testmap plaatsen we de eenvoudigste bibliotheek.


Stap 2. Plan je aanval


Afbeelding met dank aan http://connections.smsd.org/veterans

De tweede stap, die eigenlijk de belangrijkste is, is om te beginnen met het maken van uw tests. Daarvoor moet je echt plannen en nadenken over wat je functie gaat doen, welke mogelijke invoer het krijgt en de bijbehorende uitgangen die het zal verzenden. Deze stap lijkt op het spelen van een schaakspel - je moet alles weten over je tegenstander (het programma), inclusief al zijn zwakke punten (mogelijke fouten) en sterke punten (wat gebeurt er als het met succes wordt uitgevoerd).

Dus voor onze gastenboektoepassing, laten we de schema's vastleggen:

Uitzicht

  • Deze functie zal geen ingangen hebben omdat het alle gegevens uit de database zal ophalen en de af te drukken gegevens zal terugsturen.
  • Het zal een reeks gastenboekrecords retourneren, met vermelding van de naam van de poster en zijn bericht. Als er geen records zijn, moet deze nog steeds een lege array retourneren.
  • Als er records zijn, bevat de array 1 of meer waarden.
  • Tegelijkertijd zal de array een specifieke structuur hebben, zoiets als:
 Array ([0] => Array (['name'] = "Bob" ['message'] = "Hallo, ik ben Bob.") [1] => Array (['name'] = "Tom" ['message'] = "Hallo, ik ben Tom."))

Stap 3. Schrijf een test!


Afbeelding met dank aan http://cflhomeless.wordpress.com

Nu kunnen we onze eerste test schrijven. Laten we beginnen met het creëren van een bestand met de naam guestbook_test.php in de testmap.

  

Laten we vervolgens converteren wat we hebben vastgesteld vanaf stap twee,.

 add ("Bob", "Hallo, ik ben Bob."); $ guestbook-> add ("Tom", "Hallo, ik ben Tom."); $ entries = $ guestbook-> viewAll (); $ count_is_greater_than_zero = (tel ($ entries)> 0); $ This-> assertTrue ($ count_is_greater_than_zero); $ this-> assertIsA ($ entries, 'array'); foreach ($ entries as $ entry) $ this-> assertIsA ($ entry, 'array'); $ This-> assertTrue (isset ($ binnenkomst [ 'naam'])); $ This-> assertTrue (isset ($ binnenkomst [ 'boodschap']));  function testViewGuestbookWithNoEntries () $ guestbook = new Guestbook (); $ Guestbook-> Verwijder (); // Verwijder eerst alle vermeldingen zodat we weten dat het een lege tabel is $ entries = $ guestbook-> viewAll (); $ this-> assertEqual ($ entries, array ()); 

Beweringen zorgen ervoor dat een bepaald ding is wat het hoort te zijn - eigenlijk zorgt het ervoor dat wat wordt teruggegeven, is wat je verwacht dat het terugkeert. Als een functie bijvoorbeeld de waarde true moet teruggeven als deze is geslaagd, moeten we dat in onze test doen beweren dat de retourwaarde gelijk is aan true.

Zoals je hier kunt zien, testen we de weergave van het gastenboek met items en zonder. We controleren of deze twee scenario's voldoen aan onze criteria van stap twee. U hebt waarschijnlijk ook opgemerkt dat elk van onze testfuncties begint met het woord 'testen'. We hebben dit gedaan omdat, wanneer SimpleTest deze klasse uitvoert, het alle functies zal zoeken die beginnen met het woord 'testen' en het uitvoeren.

In onze testklasse hebben we ook een aantal assertiemethoden gebruikt, zoals assertTrue, assertIsA en assertEquals. De functie assertTrue controleert of een waarde waar is. AssertIsA controleert of een variabele van een bepaald type of klasse is. En ten slotte, assertEquals controleert of een variabele volledig gelijk is aan een bepaalde waarde.

Er zijn andere assertiemethoden die worden aangeboden door SimpleTest, die zijn:

assertTrue ($ x) Mislukt als $ x false is
assertFalse ($ x) Mislukt als $ x waar is
assertNull ($ x) Mislukt als $ x is ingesteld
assertNotNull ($ x) Mislukt als $ x niet is ingesteld
assertIsA ($ x, $ t) Mislukt als $ x niet de klasse of het type $ t is
assertNotA ($ x, $ t) Mislukt als $ x van de klasse is of typ $ t
assertEqual ($ x, $ y) Mislukt als $ x == $ y onwaar is
assertNotEqual ($ x, $ y) Mislukt als $ x == $ y waar is
assertWithinMargin ($ x, $ y, $ m) Fail als abs ($ x - $ y) < $m is false
assertOutsideMargin ($ x, $ y, $ m) Fail als abs ($ x - $ y) < $m is true
assertIdentical ($ x, $ y) Mislukt als $ x == $ y fout is of als het type niet overeenkomt
assertNotIdentical ($ x, $ y) Mislukt als $ x == $ y waar is en typen overeenkomen
assertReference ($ x, $ y) Fail tenzij $ x en $ y dezelfde variabele zijn
assertClone ($ x, $ y) Mislukt tenzij $ x en $ y identieke exemplaren zijn
assertPattern ($ p, $ x) Fail tenzij de regex $ p overeenkomt met $ x
assertNoPattern ($ p, $ x) Mislukt als de regex $ p overeenkomt met $ x
expectError ($ x) Slikt eventuele aankomende overeenkomende fouten door
beweren ($ e) Mislukt op mislukt verwachtingsobject $ e

Assertiemethode met dank aan http://www.simpletest.org/en/unit_test_documentation.html


Stap 4. Niet winnen


Afbeelding met dank aan http://verydemotivational.com

Als u klaar bent met het schrijven van de code, moet u de test uitvoeren. De eerste keer dat u de test uitvoert, is het ZOU FOUTEN. Als dat niet het geval is, betekent dit dat uw test niet echt iets test.

Om uw test uit te voeren, gewoon uitvoeren guestbook_test.php in uw browser. Dit zou je eerst moeten zien:

Dit is gebeurd omdat we onze gastenboekklasse nog niet hebben gemaakt. Hiertoe maakt u guestbook.php in uw klassenmap. De klasse moet de methoden bevatten die we van plan zijn te gebruiken, maar mag in eerste instantie niets bevatten. Vergeet niet dat we eerst de tests schrijven voor het schrijven van elke code.

  

Wanneer u de test opnieuw uitvoert, ziet deze er ongeveer zo uit:

Zoals we hier kunnen zien, is onze test nu aan het winnen door te falen. Dit betekent dat onze test nu klaar is om 'beantwoord' te worden.


Afbeelding met dank aan http://www.gamercastnetwork.com/forums

Stap 5. Beantwoord uw test door code te schrijven


Op een gegeven moment voelen we ons allemaal zo wanneer we programmeren.
Afbeelding met dank aan http://fermentation.typepad.com/fermentation

Nu we een werkende geautomatiseerde test hebben, kunnen we beginnen met het schrijven van code. Open je guestbook.php klasse en begin met het maken van het antwoord op uw test.

  'Kirk', 'bericht' => 'Hallo, ik ben Kirk.' ), array ('name' => 'Ted', 'message' => 'Hallo, ik ben Ted.')); public function viewAll () // Hier moeten we alle records uit de database ophalen. // Dit wordt gesimuleerd door de $ _entries array return self :: $ _ entries te retourneren;  public function add ($ name, $ message) // Hier simuleren we insertie in de database door een nieuw record toe te voegen aan de array $ _entries // Dit is de juiste manier om het te doen: self :: $ _ entries [] = array ('naam' => $ naam, 'bericht' => $ bericht); self :: $ _ entries [] = array ('notname' => $ name, 'notmessage' => $ message); // oeps, er is hier ergens een bug die waar is;  public function deleteAll () // We hebben zojuist de array $ _entries ingesteld om zelf te simuleren :: $ _ entries = array (); geef waar terug; 

Deze guestbook.php-klasse bevat expres bepaalde bugs, zodat we kunnen zien hoe het eruit ziet als onze test mislukt.

Zodra we onze test hebben uitgevoerd, zouden we zoiets als dit moeten zien:

De testoutput laat ons zien in welke test en in welke bewering onze code faalde. Hieruit kunnen we eenvoudig vaststellen dat regel 16 en 17 de bewering waren die de fout gooide.

 assertTrue (isset ($ binnenkomst [ 'naam'])); $ This-> assertTrue (isset ($ binnenkomst [ 'boodschap'])) ;? 

Dit vertelt ons duidelijk dat de geretourneerde entry-array niet de juiste array-sleutel had. Op basis hiervan zullen we gemakkelijk weten welk deel van onze code fout is gegaan.

  $ naam, 'bericht' => $ bericht); // opgelost! geef waar terug; ? 

Nu, als we onze test opnieuw uitvoeren, moet dit ons laten zien:


Stap 6. Refactoreer en verfijn uw code


Afbeeldingen met dank aan http://www.osborneink.com en http://phuketnews.phuketindex.com

Omdat de code die we hier testen vrij eenvoudig is, duurde het testen en repareren van bugs niet lang. Maar als dit een complexere toepassing was, zou u uw code meerdere keren moeten wijzigen, zodat deze schoner moet worden, zodat deze eenvoudiger te onderhouden is en nog veel meer. Het probleem hiermee is echter dat verandering meestal extra bugs introduceert. Hier komt onze geautomatiseerde test binnen - als we eenmaal wijzigingen hebben aangebracht, kunnen we de test gewoon opnieuw uitvoeren. Als het nog steeds slaagt, betekent dit dat we niets hebben gebroken. Als het mislukt, weten we dat we een fout hebben gemaakt. Het informeert ons ook waar het probleem ligt en, hopelijk, hoe we het kunnen oplossen.


Stap 7. Spoelen en herhalen


Afbeelding met dank aan http://www.philstockworld.com

Uiteindelijk, wanneer uw programma nieuwe functionaliteit vereist, moet u nieuwe tests schrijven. Dat is eenvoudig! Spoel en herhaal de procedures vanaf stap twee (aangezien je SimpleTest-bestanden al zouden moeten zijn ingesteld) en begin de cyclus helemaal opnieuw.


Conclusie

Er zijn veel meer diepgaande testgestuurde ontwikkelingsartikelen die er zijn, en nog meer functionaliteit voor SimpleTest dan wat in dit artikel werd getoond, zaken zoals mock-objecten, stubs, die het gemakkelijker maken om tests te maken. Als je meer wilt lezen, zou de testgestuurde ontwikkelingspagina van Wikipedia je op het juiste pad moeten brengen. Als u graag SimpleTest gebruikt als testomgeving, bladert u door de online documentatie en bekijkt u de andere functies.

Testen is een integraal onderdeel van de ontwikkelingscyclus, maar het is al te vaak het eerste dat wordt onderbroken wanneer er deadlines zijn. Hopelijk, na het lezen van dit artikel, zult u waarderen hoe nuttig het is om te investeren in testgestuurde ontwikkeling.

Wat denk je over testgestuurde ontwikkeling? Is het iets dat je wilt implementeren, of denk je dat het tijdverspilling is? Laat het me weten in de comments!