2010-10-18

BDD med StoryQ

Som en opfølgning på Simpel Behavior Driven Development posten, vil jeg her vise hvordan testcasen kunne se ud, hvis man istedet for havde brugt StoryQ.

[TestMethod]
public void AdditionTest()
{
new Story("Addition of numbers")
    .InOrderTo("Calculate a sum")
    .AsA("Accountant")
    .IWant("to add two numbers")
 
    .WithScenario("adding a positive number with a positive number")
    .Given(ACalculator)
    .When(_IsAddedTo_, 1, 2)
    .Then(TheResultShouldBe_,3)
 
    .WithScenario("adding a zero with a positive number")
    .Given(ACalculator)
    .When(_IsAddedTo_,0,2)
    .Then(TheResultShouldBe_,0)
 
    .WithScenario("adding a negative number with a positive number")
    .Given(ACalculator)
    .When(_IsAddedTo_,-1,2)
    .Then(TheResultShouldBe_,0)
    .ExecuteWithReport();
}

private Calculator sut { get; set; }
private int actual { get; set; }

private void ACalculator()
{
sut = new Calculator();
}

private void _IsAddedTo_(int firstNumber, int secondNumber)
{
actual = sut.Add(firstNumber, secondNumber);
}

private void TheResultShouldBe_(int expected)
{
Assert.AreEqual(expected, actual);
}

Når man kører denne test med MSTest får man automatisk genereret en html rapport med følgende information:


Hvis man sammenligner med resultatet fra den simple metode, kan man se at der her er fokuseret på det væsentlige nemlig om kravene er opfyldt.


Samt at User Storyen med tilhørende krav er bedre beskrevet på denne måde.

Hvis man tager i betragtning at det eneste der skal til for at få dette resultat er at inkludere en ekstra assembly i sit testprojekt, så synes jeg at det virkelig er noget der kan bruges til noget.

Det kræver selvfølgelig en lidt anderledes måde at skrive testcases på, men egentlig ikke noget der kræver væsentligt flere kodelinier, måske endda færre.

I dette tilfælde steg antal kodelinier med 50%, men hvis man havde forsøgt at beskrive @bakkegaard's eksempel fra den tidligere blogpost med begge metoder, ville det nok nærmere være 50% færre kodeliner.

2010-10-17

Simpel Behavior Driven Development

Her er et eksempel på en meget simpel metode til at at lave BDD style tests med MS Test frameworket.

Der findes utallige komponenter til at at lave mere flydende og måske også mere læsbare test cases til BDD, men jeg lide denne mere simple metode, som ikke kræver andet end diciplin fra dem der skriver test koden og så selvfølgelig gode user stories at tage udgangspunkt i.

Jeg har her til lejligheden opfundet en ny regnemaskine, der har en række krav, som afviger lidt fra en normal lommeregner.

Feature: Addition

 User story: 
  As an accountant I would like to add two numbers.

 Acceptance Criteria:
  Given a positive number
  When added to a positive number
  Then the result should be the sum of the numbers

  Given a zero
  When added to a positive number
  Then the result should be zero

  Given a negative number
  When added to a positive number
  Then the result should be zero

Som man kan se er dette ikke helt almindelige krav til en regnemaskine, men det er heller ikke det der er det vigtige i denne omgang.

Disse krav kunne sagtens testes via "gammeldags" TDD og få det ønskede resultat. Men det som jeg ser som målet med BDD er at kunne vise kunden (Product Owner) at de krav som der er stillet er opfyldt, længe før at et evt brugerinterface er lavet færdigt, og dette egner de fleste testresultater fra TDD sig ikke til. Selvom man laver gode navne på sine tests og variable, så er det jo ikke ensbetydende med at de er forståelige for kunden.

Derfor kan man istedet udnytte det fulde potentiale af udviklingsmiljøet og bruge namespaces, klasse- og metodenavne til at beskrive hvilken user story med tilhørende krav der bliver testet.


Her er et eksempel på dette
namespace Feature_Addition._As_an_Accountant._I_would_like_to
{
    [TestClass]
    public class _add_two_numbers
    {

        Calculator sut;
        [TestInitializeAttribute]
        public void SetUp()
        {
            sut = new Calculator();
        }

        [TestMethod]
        public void Given_a_positive_number_When_added_to_a_positive_numbers_Then_result_should_be_the_sum()
        {
            Assert.AreEqual(3, sut.Add(1, 2));
        }

        [TestMethod]
        public void Given_a_zero_When_added_to_a_positive_number_Then_result_should_be_0()
        {
            Assert.AreEqual(0, sut.Add(0, 2));
        }

        [TestMethod]
        public void Given_a_negative_number_When_added_to_a_positive_number_Then_result_should_be_0()
        {
            Assert.AreEqual(0, sut.Add(-1, 2));
        }
    }
}

Som man kan se er hele user storyen med alle 3 acceptance criteria repræsenteret i denne test klasse. Og når man kører disse test, så får man følgende resultat i Test Results vinduet


Jeg har tilføjet kolonnen "Full Class Name" til listen, for bedre at kunne se hele user storien i test resultatet. Alternativt skal man angive hele navnet i hver test metode, men det vil kun gøre navnene besværlige at læse og  kræve at den samme sætning bliver gentaget igen og igen.

Dette test resultat kunne man selvfølgelig sagtens levere, som dokumentation for at kundens krav er opfyldt, men det er nok ikke så læsbart som man kunne ønske. Istedet kan man udnytte at MSTest laver en XML fil med resultaterne fra hver testkørsel. Denne XML fil kan man konvertere til HTML format ved hjælp af værktøjet trx2html så man istedet for får dette resultat


Her er et resultat der kan bruges til at dokumentere om kundens krav er opfyldt løbende under udviklingsfasen. Det eneste der mangler er at få denne html rapport automatisk genereret når build serveren har kørt unit tests efter en build.

2010-10-01

Test af array

Inspireret af dette indlæg på StackOverflow: http://bit.ly/atgBod har jeg forsøgt at lave 2 forskellige test metoder, for at vise forskellen mellem unit og integrationstest.

Jeg mener at forfatterens eksempel er en test der i realiteten ikke er nødvendig, hvis man istedet for at teste på værdierne i et array, istedet testede det der genererede array'et

Her er først et eksempel på integrationstesten:

[TestMethod]
public void ArrayTest()
{
    IBlackBox sut = new BlackBox();
    Assert.AreEqual(0, sut.items[0] % 2);
    for (int i = 1; i < sut.items.Length; i++)
    {
       Assert.AreEqual(sut.items[i - 1] + 2, sut.items[i]);
       Assert.AreEqual(0, sut.items[i] % 2);
    }
}

Den kunne sagtens deles op i nogle seperate tests, men som man kan se, så antager testen det array der testes på allerede eksisterer og at værdierne er fyldt i uden egentlig at teste hvordan arrayet er genereret.

Hvis man ser hvordan arrayet bliver genereret kan man pludselig se en del flere hjørner der bør testes, fx negative værdier og ulige tal.

public class WhiteBox : IWhiteBox
{
    public int GenerateNextItem(int previousItem)
    {
        if (previousItem < 0) return 0;
        if (previousItem % 2 != 0) previousItem--;
        int nextItem = previousItem += 2;
        return nextItem;
    }
}
 
public class BlackBox : IBlackBox
{
    public int[] items
    {
        get
        {
            IWhiteBox whiteBox = new WhiteBox();
            int value = 0;
            IList<int> result = new List<int>();
            for (int i = 0; i < 4; i++)
            {
                value = whiteBox.GenerateNextItem(value);
                result.Add(value);
            }
            return result.ToArray();
        }
    }
}

De tilhørende tests til denne implementering kunne så være

[TestMethod]
public void GivenUnevenPreviousValueReturnsNextEvenValue()
{
    IWhiteBox sut = new WhiteBox();
    int actual = sut.GenerateNextItem(1);
    int expected = 2;
    Assert.AreEqual(expected, actual);
}

[TestMethod]
public void GivenEvenPreviousValueReturnsNextEvenValue()
{
    IWhiteBox sut = new WhiteBox();
    int actual = sut.GenerateNextItem(2);
    int expected = 4;
    Assert.AreEqual(expected, actual);
}

[TestMethod]
public void GivenNegativePreviousValueReturnsZero()
{
    IWhiteBox sut = new WhiteBox();
    int actual = sut.GenerateNextItem(-1);
    int expected = 0;
    Assert.AreEqual(expected, actual);
}

Nu er der selvfølgelig en del flere tests, til "bare" at teste en så simpel funktion, tilgengæld så er der ingen tvivl om hvordan arrayet bliver genereret og at "ulovlige" værdier er håndteret, hvis nogen skulle finde på at ændre i implementeringen af array genereringen. Den første test ville fx ikke finde fejl ved negative numre i arrayet.

2010-03-09

TinyIoC - Lille sej DI container

Jeg faldt fornylig over en lille simple DI container kaldet TinyIoCtwitter. filosofien bag denne DI container er, at man ikke behøver at skulle tilføje en ekstra assembly (eller flere) for at kunne bruge en DI container. Den er selvfølgelig heller ikke egnet til løsninger med mange og komplekse afhængigheder, men til mindre løsninger kan den sagtens anvendes.

Her er et eksempel på hvor simpelt det er at anvende den. Jeg har lavet et konsolprojekt og har kopieret TinyIoC.cs ind i projektet.

Jeg har herefter lavet et par interfaces og klasser, som jeg vil benytte TinyIoC til at oprette instanser af. Selvom det er nogle små klasser og et lidt specialt mønster, håber jeg at man kan umiddelbart kan gennemskue dette - her er et klassediagram over disse klasser:


For at initalisere en instans af Cat på traditionel vis (Poor man's DI) skal jeg skrive følgende:

Farmer farmer = new Farmer();
Milk milk = new Milk(farmer);
IFood food = new Food();
ICat cat = new Cat(food,milk);

Hvis jeg istedet anvender TinyIoC containeren til at initialisere en Cat kan jeg nøjes med at skrive følgende:

var container = TinyIoCContainer.Current;
container.AutoRegister();
ICat cat = container.Resolve<ICat>();

Jeg benytter her muligheden for at auto registere objekterne og interfacene i DI containeren, jeg kunne også have regisreret dem enkeltvis, men det ville i dette tilfælde have gjort eksemplet for komplekst, til at man ville kunne se en fordel ved at bruge TinyIoC istedet for Poor Man's DI.

2010-02-25

Covariance og Contravariance (1 af 2)

Introduktion

I de næste 2 poster vil jeg forsøge at forklare hvordan covariance og contravariance kan anvendes i C#4 - selve begreberne covariance og contravariance er for komplekse til at forklare her, jeg vil istedet henvise til denne beskrivelse på wikipedia: Covariance_and_contravariance_(computer_science).

I denne første post vil jeg vise nogle eksempler på hvordan covariance fungerer i C#4.

Covariance

Her er et eksempel på simpel covariance. Jeg anvender her IEnumerable som understøtter covariance i C#4

interface ICoffee : IBeverage { }
interface IBeverage { }

[TestClass]
public class Covariance_Test
{
    [TestMethod]
    public void Simple_Covariance_Test()
    {
        IEnumerable<ICoffee> coffees = new List<ICoffee>();
        IEnumerable<IBeverage> beverages = coffees;
        Assert.AreEqual(coffees, beverages);
    }
}

Hvis man prøver at udføre det samme i C#3.0, vil man se at der kommer en kompilefejl, når man forsøger at tildele coffees til variablen beverages

Dette er meget simpelt eksempel, men er sandsynligvis nok det scenearie, hvor man oftest vil anvende denne nye funktionalitet. Man har også muligheden for at lade sine egne objekter understøtte covariance. Her er et eksempel hvor man ønsker at have nogle lister af objekter, der har det samme interface og vil kunne gennemgå data i disse lister uden at skulle lave en explicit cast.

Først er der interfacet og de klasser der implementerer dette interface
interface IDog
{
    string Name { get; set; }
}

class Beagle : IDog
{
    public string Name { get; set; }
}

class Basset : IDog
{
    public string Name { get; set; }
}

Herefter interfacet der beskriver hvordan listerne skal implementeres. Læg her mærke til keywordet "out" der fortæller at jeg ønsker at bruge paramteren T som en out parameter, dvs ikke kunne skrive direkte til den.
interface IKennel<out T> where T : IDog
{
    IEnumerable<T> dogs { get; }
}

Jeg har herefter lavet 2 klasser der implementere dette interface
class BeagleKennels : IKennel<Beagle>
{
    List<Beagle> beagles = new List<Beagle>();
    
    public IEnumerable<Beagle> dogs { get { return beagles; } }

    public BeagleKennels AddBeagle(Beagle beagle)
    {
        this.beagles.Add(beagle);
        return this;
    }
}

class BassetKennel : IKennel<Basset>
{
    List<Basset> bassets = new List<Basset>();

    public IEnumerable<Basset> dogs { get { return bassets; } }

    public BassetKennel AddBasset(Basset basset)
    {
        this.bassets.Add(basset);
        return this;
    }
}

Når jeg så anvender disse 2 Kennel typer i praksis, kan man se hvordan man nemt kan iterere over indholdet af disse 2 kenneler, uden at skulle lave en explicit cast til IDog
[TestMethod]
public void Custom_Covariance_Test()
{
    var TheBeagleKennel = new BeagleKennels()
        .AddBeagle(new Beagle() { Name = "C1_1" })
        .AddBeagle(new Beagle() { Name = "C1_2" });

    var TheBassetKennel = new BassetKennel()
        .AddBasset(new Basset() {Name="C2_1"})
        .AddBasset(new Basset() {Name="C2_2"});
    
    var classDs=new IKennel<IDog>[] {TheBeagleKennel,TheBassetKennel};
    var dogs= from kennel in kennels from dog in kennel.dogs select dog;
    foreach (var dog in dogs)
    {
       Assert.IsInstanceOfType(dog,typeof(IDog));
    }
}

Næste post

I næste post vil jeg vise nogle lignende eksempler på hvordan man kan anvende contravariance i C#4