Saturday, September 20, 2014

Getting started with BDD and NUnit testing

 It appears (IMHO, and Googlers') that MS's Test Explorer is shiite.  
 Better runners are: NUnit, TestDriven.NET, ReSharper, etc.  
 (Optional)  
 Writing Behaviour Driven Development tests:  
  - To Install BDDfy (http://docs.teststack.net/bddfy/),   
    run from the NuGet Package Manager Console:   
     Install-Package TestStack.BDDfy  

 Writing Unit Tests:  
  - Create a .UnitTests VisualStudio Project for each Project you wish to test  
  - Use the NUnit syntax (i.e. annotations, see included book or google it) in your tests  
  - To Install NUnit (for the Tests Project, not the SystemUnderTest), run from the NuGet Package Manager Console:   
    Install-Package NUnit  

 Running (and Debugging) Unit Tests in VS:  
  - To install TestDriven.NET (a Test runner; ReSharper is another), run the installer EXE.  
  - Use TestDriven.NET in VisualStudio context menu   
    i.e. right-click in the Solution and/or Project containing tests or right in a test method in the file Editor   
       and select:  
       - 'Run Test(s)' to run i.e. NOT 'Run Tests' [ONLY when in Editor], that uses Microsofts runner...  
       - 'Test With > Debugger' to debug  

Sample code follows:

Largest.cs

 using System;  
 namespace Largest  
 {  
   public class Cmp  
   {  
     public static int Largest(int[] list)  
     {  
       if (list.Length == 0)  
       {  
         throw new ArgumentException("Empty list");  
       }  
       int index, max = list[0];  
       for (index = 0; index < list.Length; index++)  
       {  
         if (list[index] > max)  
         {  
           max = list[index];  
         }  
       }  
       return max;  
     }  
   }  
 }  

LargestTest.cs

 using System;  
 using NUnit.Framework;  
 {  
   [TestFixture]  
   public class LargestTest  
   {  
     [Test]  
     public void LargestOf3()  
     {  
       int[] numbers = new int[] { 9, 8, 7 };  
       Assert.That(Cmp.Largest(numbers), Is.EqualTo(9));  
       numbers = new int[] { 8, 9, 7 };  
       Assert.That(Cmp.Largest(numbers), Is.EqualTo(9));  
       numbers = new int[] { 7, 8, 9 };  
       Assert.That(Cmp.Largest(numbers), Is.EqualTo(9));  
     }  
     [Test]  
     [ExpectedException(typeof(ArgumentException))]  
     public void Empty()  
     {  
       Cmp.Largest(new int[] { });  
     }  
     [Test]  
     public void LargestOf3Alt()  
     {  
       Assert.That(Cmp.Largest(new int[]{ 8, 9, 7 }), Is.EqualTo(9));  
     }  
     [Test]  
     public void Negative()  
     {  
       int[] negatives = new int[] { -9, -8, -7 };  
       Assert.That(Cmp.Largest(negatives), Is.EqualTo(-7));  
     }  
   }  
 }  

BDDDemo.cs

 using System;  
 using Microsoft.VisualStudio.TestTools.UnitTesting;  
 using TestStack.BDDfy;  
 namespace BDDfy.Samples.Atm // Scenarios taken from http://dannorth.net/introducing-bdd/  
 {  
   [TestClass]  
   [Story(  
     AsA = "As an Account Holder",  
     IWant = "I want to withdraw cash from an ATM",  
     SoThat = "So that I can get money when the bank is closed")]  
   public class AccountHolderWithdrawsCash  
   {  
     [TestMethod]  
     public void AccountHasInsufficientFund()  
     {  
       new AccountHasInsufficientFund().BDDfy();  
     }  
     [TestMethod]  
     public void CardHasBeenDisabled()  
     {  
       new CardHasBeenDisabled().BDDfy();  
     }  
     [TestMethod]  
     public void AccountHasSufficientFund()  
     {  
       new AccountHasSufficientFund()  
         .Given(s => s.GivenTheAccountBalanceIs(100), "Given the account balance is $100")  
           .And(s => s.AndTheCardIsValid())  
           .And(s => s.AndTheMachineContainsEnoughMoney())  
         .When(s => s.WhenTheAccountHolderRequests(20), "When the account holder requests $20")  
         .Then(s => s.ThenTheAtmShouldDispense(20), "Then the ATM should dispense $20")  
           .And(s => s.AndTheAccountBalanceShouldBe(80), "And the account balance should be $80")  
           .And(s => s.AndTheCardShouldBeReturned())  
       .BDDfy();  
     }  
   }  
   [TestClass]  
   public class AccountHasSufficientFund  
   {  
     private Card _card;  
     private Atm _atm;  
     public void GivenTheAccountBalanceIs(int balance)  
     {  
       _card = new Card(true, balance);  
     }  
     public void AndTheCardIsValid()  
     {  
     }  
     public void AndTheMachineContainsEnoughMoney()  
     {  
       _atm = new Atm(100);  
     }  
     public void WhenTheAccountHolderRequests(int moneyRequest)  
     {  
       _atm.RequestMoney(_card, moneyRequest);  
     }  
     public void ThenTheAtmShouldDispense(int dispensedMoney)  
     {  
       Assert.AreEqual(dispensedMoney, _atm.DispenseValue);  
     }  
     public void AndTheAccountBalanceShouldBe(int balance)  
     {  
       Assert.AreEqual(balance, _card.AccountBalance);  
     }  
     public void AndTheCardShouldBeReturned()  
     {  
       Assert.IsFalse(_atm.CardIsRetained);  
     }  
   }  
   [TestClass]  
   public class AccountHasInsufficientFund  
   {  
     private Card _card;  
     private Atm _atm;  
     // You can override step text using executable attributes  
     [Given("Given the account balance is $10")]  
     void GivenTheAccountBalanceIs10()  
     {  
       _card = new Card(true, 10);  
     }  
     void AndGivenTheCardIsValid()  
     {  
     }  
     void AndGivenTheMachineContainsEnoughMoney()  
     {  
       _atm = new Atm(100);  
     }  
     [When("When the account holder requests $20")]  
     void WhenTheAccountHolderRequests20()  
     {  
       _atm.RequestMoney(_card, 20);  
     }  
     void ThenTheAtmShouldNotDispenseAnyMoney()  
     {  
       Assert.AreEqual(0, _atm.DispenseValue);  
     }  
     void AndTheAtmShouldSayThereAreInsufficientFunds()  
     {  
       Assert.AreEqual(DisplayMessage.InsufficientFunds, _atm.Message);  
     }  
     void AndTheAccountBalanceShouldBe10()  
     {  
       Assert.AreEqual(10, _card.AccountBalance);  
     }  
     void AndTheCardShouldBeReturned()  
     {  
       Assert.IsFalse(_atm.CardIsRetained);  
     }  
     [TestMethod]  
     public void Execute()  
     {  
       this.BDDfy();  
     }  
   }  
   [TestClass]  
   public class CardHasBeenDisabled  
   {  
     private Card _card;  
     Atm _subject;  
     void GivenTheCardIsDisabled()  
     {  
       _card = new Card(false, 100);  
       _subject = new Atm(100);  
     }  
     void WhenTheAccountHolderRequestsMoney()  
     {  
       _subject.RequestMoney(_card, 20);  
     }  
     void ThenTheAtmShouldRetainTheCard()  
     {  
       Assert.IsTrue(_subject.CardIsRetained);  
     }  
     void AndTheAtmShouldSayTheCardHasBeenRetained()  
     {  
       Assert.AreEqual(DisplayMessage.CardIsRetained, _subject.Message);  
     }  
     [TestMethod]  
     public void Execute()  
     {  
       this.BDDfy();  
     }  
   }  
 }  

BDDDomainObjects

 namespace BDDfy.Samples.Atm  
 {  
   public class Atm  
   {  
     public int ExistingCash { get; private set; }  
     public Atm(int existingCash)  
     {  
       ExistingCash = existingCash;  
     }  
     public void RequestMoney(Card card, int request)  
     {  
       if (!card.Enabled)  
       {  
         CardIsRetained = true;  
         Message = DisplayMessage.CardIsRetained;  
         return;  
       }  
       if (card.AccountBalance < request)  
       {  
         Message = DisplayMessage.InsufficientFunds;  
         return;  
       }  
       DispenseValue = request;  
       card.AccountBalance -= request;  
     }  
     public int DispenseValue { get; set; }  
     public bool CardIsRetained { get; private set; }  
     public DisplayMessage Message { get; private set; }  
   }  
   public class Card  
   {  
     public int AccountBalance { get; set; }  
     private readonly bool _enabled;  
     public Card(bool enabled, int accountBalance)  
     {  
       AccountBalance = accountBalance;  
       _enabled = enabled;  
     }  
     public bool Enabled  
     {  
       get { return _enabled; }  
     }  
   }  
   public enum DisplayMessage  
   {  
     None = 0,  
     CardIsRetained,  
     InsufficientFunds  
   }  
 }  

Sample output

 ------ Test started: Assembly: UnitTests.dll ------  
 Story: Account holder withdraws cash  
      As an Account Holder  
      I want to withdraw cash from an ATM  
      So that I can get money when the bank is closed  
 Scenario: Account has insufficient fund  
      Given the account balance is $10  
       And the card is valid  
       And the machine contains enough money  
      When the account holder requests $20  
      Then the atm should not dispense any money  
       And the atm should say there are insufficient funds  
       And the account balance should be 10  
       And the card should be returned  
 Story: Account holder withdraws cash  
      As an Account Holder  
      I want to withdraw cash from an ATM  
      So that I can get money when the bank is closed  
 Scenario: Card has been disabled  
      Given the card is disabled  
      When the account holder requests money  
      Then the atm should retain the card  
       And the atm should say the card has been retained  
 Story: Account holder withdraws cash  
      As an Account Holder  
      I want to withdraw cash from an ATM  
      So that I can get money when the bank is closed  
 Scenario:   
      Given the account balance is $100  
       And the card is valid  
       And the machine contains enough money  
      When the account holder requests $20  
      Then the ATM should dispense $20  
       And the account balance should be $80  
       And the card should be returned  
 3 passed, 0 failed, 0 skipped, took 0.89 seconds (MSTest 10.0).