Ett TDD-exempel: nedräkning

Jag bygger ett spel som heter Dogfight2008. Där behövde jag ikväll göra så att det blev en ”timeout” varje gång man sköt ett skott; alltså en omladdningstid (man kan inte skjuta hur ofta som helst).

Så jag knöt nävarna och började koda på flygplanets logik för detta. Men när jag kommit ungefär halvvägs kom jag på att det jag ville ha var en ”Countdown”-klass som hjälpte mig med denna lilla uppgift, som planet sedan använde sig av enkelt och lätt.

Jag brukar ha en ganska klar bild av vad jag är ute efter matematiskt, innan jag ger mig in på utvecklandet. I ord ville jag ha en klass utan några beroenden, där det går att ”räkna ned” och ”kolla om nedräkning är klar”. Det som behövdes i Dogfights flygplan, efter man avfyrat ett skott, kort sagt.

Sagt och gjort slängde jag ihop första testet, i sann TDD-anda:

[Test]
public void Construct()
{
  Countdown c = new Countdown(10);
}

Detta lilla test genererade följande minimala klass:

public class Countdown
{
  public Countdown(double time) { }
}

Nästa test kontrollerar så att ”Stopped”-propertien inte är satt från början:

[Test]
public void StoppedIsFalseFromBeginning()
{
  Countdown c = new Countdown(10);
  Assert.IsFalse(c.Stopped);
}

Med ”elaka hatten” på huvudet gör jag så lite jag kan för att uppfylla detta krav:

public class Countdown
{
  public Countdown(double time) { }
  public bool Stopped = false;
}

Här kom jag på att jag endast ville tillåta positiva tidsperioder hos Countdowns:

[Test, ExpectedException]
public void TimeMustBePositive()
{
  new Countdown(0);
}

Så:

public class Countdown
{
  public Countdown(double time)
  {
  if(time <= 0) throw new Exception();   } public bool Stopped = false; } [/sourcecode] Första "verkliga" fallet blir att nedräkningen går till noll i ett steg: [sourcecode language="csharp"] [Test] public void StoppedBecomesTrueAfterTimeHasPassed_InOneStep() {   Countdown c = new Countdown(10);   c.Tick(10);   Assert.IsTrue(c.Stopped); } [/sourcecode] Fortfarande kan jag leka ond och lura testerna: [sourcecode language="csharp"] public class Countdown {   public Countdown(double time)   {   if (time <= 0)   throw new Exception();   } private stopped = false;   public bool Stopped   {   get   {   return stopped;   }   } public void Tick(double deltaTime)   {   stopped = true;   } } [/sourcecode] Nästa test gör det svårare att "fakea": [sourcecode language="csharp"] [Test] public void StoppedBecomesTrueAfterTimeHasPassed_InTwoSteps() {   Countdown c = new Countdown(10);   c.Tick(8);   c.Tick(3);   Assert.IsTrue(c.Stopped); } [/sourcecode] Jag orkar inte hålla igen det oundvikliga och tar med en time-variabel i klassen, och tar bort den tillfälliga stopped-variabeln: [sourcecode language="csharp"] public class Countdown {   double time; public Countdown(double time)   {   if (time <= 0)   throw new Exception(); this.time = time;   } public bool Stopped   {   get   {   return time <= 0;   }   } public void Tick(double deltaTime)   {   time -= deltaTime;   } } [/sourcecode] För att vara än mer säker på min klass lägger jag till ett sista litet test: [sourcecode language="csharp"] [Test] public void DoesNotStopWithSmallTick() {   Countdown c = new Countdown(10);   c.Tick(1);   Assert.IsFalse(c.Stopped); } [/sourcecode] Samtliga test i en följd: [sourcecode language="csharp"] using NUnit.Framework; namespace Dogfight2008.Tests {   [TestFixture]   public class CountdownFixture   {   [Test]   public void Construct()   {   Countdown c = new Countdown(10);   } [Test]   public void StoppedIsFalseFromBeginning()   {   Countdown c = new Countdown(10);   Assert.IsFalse(c.Stopped);   } [Test, ExpectedException]   public void TimeMustBePositive()   {   new Countdown(0);   } [Test]   public void StoppedBecomesTrueAfterTimeHasPassed_InOneStep()   {   Countdown c = new Countdown(10);   c.Tick(10);   Assert.IsTrue(c.Stopped);   } [Test]   public void StoppedBecomesTrueAfterTimeHasPassed_InTwoSteps()   {   Countdown c = new Countdown(10);   c.Tick(8);   c.Tick(3);   Assert.IsTrue(c.Stopped);   } [Test]   public void DoesNotStopWithSmallTick()   {   Countdown c = new Countdown(10);   c.Tick(1);   Assert.IsFalse(c.Stopped);   }   } } [/sourcecode] Läs även andra bloggares åsikter om , ,

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: