Saturday, April 04, 2009

Behaviour Driven Design

Aku sudah lama apabila menulis code test guna cara TDD, cuma baru-baru nie adalah dalam setahun yang lepas aku nampak TDD banyak sangat repeated test code, code smell etc. Dan lagi satu bila buat test tu tak berapa sangat nak clear apa yang code itu test dan akhir sekali banyak sekali test code aku yang terbengkalai kerana masa nak deliver semakin dekat jadi terus tumpukan kepada real code. Akhir-akhir ini aku tumpukan masa lebih sedikit tentang BDD dan aku tertarik untuk menjadi sebahagian dari community BDD.

Bila dah involve dengan BDD, sudah tentu code test yang ditulis berlainan dari apa yang tedapat dalam TDD. Kenapa aku beria-ia sangat nak tukar ialah kerana bila aku buat BDD, test code aku boleh menajdi sebahagian dari "Executable Documentation". Aku kesian kat team aku dahulu, oleh kerana ketidakmahiran aku dalam menulis documentation maka berlari-lari keluar mereka mencari rumah baru sedang aku masih dalam lamunan dengan memberi janji-janji manis bahawa aku mempunya standard yang tinggi dalam software development hehehehe.............

Aku insaf seketika, memikirkan bagaimana bisa aku hendak cari penganti mereka-mereka yang hebat-hebat ini? Aku kalau diberi peluang..arghh jangan dikenang perkara yang lalu...

Apa yang aku bernostalgia nie, anak-anak aku semua dah tidur, mata aku tidak mengantuk lagi. Aku sambung semula tentang BDD.

Untuk aku code guna BDD aku boleh memilih 2 jalan alternatif, alternatif pertama dari kumpulan parti Dan North dan alternatif yang lagi satu ialah parti David Astel, kedua-dua mereka ini tak bertanding dimana-mana bukit dan batang seperti pilihanraya di Malaysia.

Pada mulanya aku cuba undi cara Dan North, sebab dia menjanjikan bahawa rakyat yang buat business prosess dan rakyat marhaen seperti programmer akan dapat menikmati kenikmatan cara Narrator/Story. Tapi selepas diberi peluang memang benar kenikmatan bagi pihak business prosess tapi rakyat marhaen kurang mendapat pembelaan, sebagai ada satu perasaan memberontak kerana untuk mendapat gaji mereka, mereka dikehendaki menulis karangan seperti ini:

Story: Deposit

Narrative:
As a User
I want The bank account balance to increase by the amount deposited
So that I can deposit money

Scenario 1: Money deposit
Given My bank account is empty
When I deposit 100 units
Then The account balance should be 100

Scenario 2: Negative amount deposit
Given My bank account is empty
When I try to deposit a negative amount
Then I get an exception - FAILED
Then the user can not log in


Selepas tu aku cuba pulak memilih parti baru menggunakan cara Context/Specification atau AAA (Arrange/Act/Assert). Buat masa nie aku rasa macam parti nie best sedikit dari parti Dan North dan sudah tentu parti BIJAN tidak menarik minat aku langsung. Aku tunjukkan dulu cara bagaimana aku write BDD guna AAA style.

Mula-mula aku create dahulu SpecificationContext.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;

namespace Icms.Tests
{
public abstract class SpecificationContext
{
[TestFixtureSetUp]
public void ContextSetup()
{
Context();
Act();
}

[TestFixtureTearDown]
public void ContextTearDown()
{
Tear_Down();
}

public virtual void Context()
{

}

public virtual void Act()
{
}

public virtual void Tear_Down()
{
}
}
}

Lepas tu aku create pulak SpecificationExtension.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using System.Collections;
using System.Xml;

namespace Icms.Tests
{
public static class SpecificationExtension
{
public delegate void MethodThatThrows();

public static void ShouldBeFalse(this bool condition)
{
Assert.IsFalse(condition);
}

public static void ShouldBeTrue(this bool condition)
{
Assert.IsTrue(condition);
}

public static object ShouldEqual(this object actual, object expected)
{
Assert.AreEqual(expected, actual);
return expected;
}

public static object ShouldNotEqual(this object actual, object expected)
{
Assert.AreNotEqual(expected, actual);
return expected;
}

public static void ShouldBeNull(this object anObject)
{
Assert.IsNull(anObject);
}

public static void ShouldNotBeNull(this object anObject)
{
Assert.IsNotNull(anObject);
}

public static object ShouldBeTheSameAs(this object actual, object expected)
{
Assert.AreSame(expected, actual);
return expected;
}

public static object ShouldNotBeTheSameAs(this object actual, object expected)
{
Assert.AreNotSame(expected, actual);
return expected;
}

public static void ShouldBeOfType(this object actual, Type expected)
{
Assert.IsInstanceOfType(expected, actual);
}

public static void ShouldNotBeOfType(this object actual, Type expected)
{
Assert.IsNotInstanceOfType(expected, actual);
}

public static void ShouldContain(this IList actual, object expected)
{
Assert.Contains(expected, actual);
}

public static void ShouldContain(this IEnumerable actual, T expected)
{
ShouldContain(actual, x => x.Equals(expected));
}

public static void ShouldContain(this IEnumerable actual, Func expected)
{
actual.Single(expected).ShouldNotEqual(default(T));
}

public static void ShouldBeEmpty(this IEnumerable actual)
{
actual.Count().ShouldEqual(0);
}

public static void ShouldHaveCount(this IEnumerable actual, int expected)
{
actual.Count().ShouldEqual(expected);
}

public static IComparable ShouldBeGreaterThan(this IComparable arg1, IComparable arg2)
{
Assert.Greater(arg1, arg2);
return arg2;
}

public static IComparable ShouldBeLessThan(this IComparable arg1, IComparable arg2)
{
Assert.Less(arg1, arg2);
return arg2;
}

public static void ShouldBeEmpty(this ICollection collection)
{
Assert.IsEmpty(collection);
}

public static void ShouldBeEmpty(this string aString)
{
Assert.IsEmpty(aString);
}

public static void ShouldNotBeEmpty(this ICollection collection)
{
Assert.IsNotEmpty(collection);
}

public static void ShouldNotBeEmpty(this string aString)
{
Assert.IsNotEmpty(aString);
}

public static void ShouldContain(this string actual, string expected)
{
StringAssert.Contains(expected, actual);
}

public static string ShouldBeEqualIgnoringCase(this string actual, string expected)
{
StringAssert.AreEqualIgnoringCase(expected, actual);
return expected;
}

public static void ShouldEndWith(this string actual, string expected)
{
StringAssert.EndsWith(expected, actual);
}

public static void ShouldStartWith(this string actual, string expected)
{
StringAssert.StartsWith(expected, actual);
}

public static void ShouldContainErrorMessage(this Exception exception, string expected)
{
StringAssert.Contains(expected, exception.Message);
}

public static Exception ShouldBeThrownBy(this Type exceptionType, MethodThatThrows method)
{
Exception exception = null;

try
{
method();
}
catch (Exception e)
{
Assert.AreEqual(exceptionType, e.GetType());
exception = e;
}

if (exception == null)
{
Assert.Fail(String.Format("Expected {0} to be thrown.", exceptionType.FullName));
}

return exception;
}

public static void ShouldEqualSqlDate(this DateTime actual, DateTime expected)
{
TimeSpan timeSpan = actual - expected;
Assert.Less(Math.Abs(timeSpan.TotalMilliseconds), 3);
}

public static object AttributeShouldEqual(this XmlElement element, string attributeName, object expected)
{
Assert.IsNotNull(element, "The Element is null");

string actual = element.GetAttribute(attributeName);
Assert.AreEqual(expected, actual);
return expected;
}

public static XmlElement ShouldHaveChild(this XmlElement element, string xpath)
{
XmlElement child = element.SelectSingleNode(xpath) as XmlElement;
Assert.IsNotNull(child, "Should have a child element matching " + xpath);

return child;
}

public static XmlElement DoesNotHaveAttribute(this XmlElement element, string attributeName)
{
Assert.IsNotNull(element, "The Element is null");
Assert.IsFalse(element.HasAttribute(attributeName), "Element should not have an attribute named " + attributeName);

return element;
}

}
}

Ok dalam AAA style nie cara susun kedudukan file akan membina "Executable Documentation" yang boleh dimanfaatkan oleh banyak pihak. Contoh structure project dan apabila di run menggunakan NUnit




NUnit Result

0 comments: