Bddify is a very small library that provides support for BDD test and generates a nice report.

BDD Simply with Bddify

[Update] I have made a LOT of changes on this and have turned it into a fully-fledged BDD framework. So if you are here because you heard of bddify framework then this is not what the framework is. To see the framework please visit its homepage on google code.

In my BDD Simply post I talked about SpecFor. SpecFor is only a few lines of code and it provides an opinionated structure within which you can write BDD-like tests. This class also generates a half-decent report for your tests.

SpecFor is very simple and easy; but I do not like its reports. I really want to have something more readable.

Enter Bddify

In an attempt to provide a better version of SpecFor and to get a nice report out, I put together a small library called Bddify. Instead of trying to explain what it is and how it works I will just show you an example. I am going to use the same ATM and disabled card example I have used in my other posts:

public class card_is_disabled
{
    private Card _card;
    Atm _subject;

    [Given]
    void the_card_is_disabled()
    {
        _card = new Card(false);
        _subject = new Atm();
    }

    [When]
    void the_account_holder_requests_money()
    {
        _subject.RequestMoney(_card);
    }

    [Then]
    void the_Atm_should_retain_the_card()
    {
        Assert.That(_subject.CardIsRetained, Is.True);
    }

    [AndThen]
    void the_Atm_should_say_the_card_has_been_retained()
    {
        Assert.That(_subject.Message, Is.EqualTo(DisplayMessage.CardIsRetained));
    }

    [Test]
    public void Execute()
    {
        this.Bddify();
    }
}

To use Bddify you need to decorate your methods with some attributes. The attribute that Bddify cares about is called ExecutableAttribute; but for the readability's sake I have added a few attributes called Given, AndGiven, When, AndWhen, Then and AndThen. You do not have to use these attributes if you do not like.

And then you will only have one test method from which you call this.Bddify(). This call Bddifies your class!

The success report

If you run the 'Execute' test you will see the following report:

alt text

Bddify generates this report for you by reflecting over the test type and its methods. If you do not like underscores in type/method names that is ok. I will provide a solution shortly.

The failure report

Let's make that test fail by changing one of the assertions:

[Then]
void the_Atm_should_retain_the_card()
{
    Assert.That(_subject.CardIsRetained, Is.False); // Is.True was changed to Is.False
}

When you run the test again you get a failure report like below:

alt text

The error and stack trace is the same as you would get if you did not use Bddify. The class also shows you which part of the test has failed by showing a "Failed" text next to the failing bit.

Support for test first development

When you are doing test first development you usually end up creating methods that you do not implement straightaway. The body of these methods usually throws a NotImplementedException.

I usually check-in my changes a bit at a time instead of waiting for a whole feature to be implemented; but I do not want my check-in to break the build. Bddify allows for that. If your code (either the test code or the classes you are testing) throw a NotImplementedException, Bddify will ignore the test for you. This way not only your check-in does not break the build; but you will also know which bits you still have not implemented. Let's try this:

[Then]
void TheAtmShouldRetainTheCard()
{
    throw new NotImplementedException();
}

I just throw an exception from one of the test methods. If you run the test now you get the following result:

alt text

Do not like method names with underscore?

Some developers just do not like underscores in method/type name, and that is quite understandable. For that I have written an extension (if you can call it that, because it is just one line) that allows you to write your tests like below:

public class CardIsDisabled
{
    private Card _card;
    Atm _subject;

    [Given]
    void TheCardIsDisabled()
    {
        _card = new Card(false);
        _subject = new Atm();
    }

    [When]
    void TheAccountHolderRequestsMoney()
    {
        _subject.RequestMoney(_card);
    }

    [Then]
    void TheAtmShouldRetainTheCard()
    {
        Assert.That(_subject.CardIsRetained, Is.True);
    }

    [AndThen]
    void TheAtmShouldSayTheCardHasBeenRetained()
    {
        Assert.That(_subject.Message, Is.EqualTo(DisplayMessage.CardIsRetained));
    }

    [Test]
    public void Execute()
    {
        Bddify.CreateSentenceFromName = BddifyExtensions.CreateSentenceCamelName;
        this.Bddify();
    }
}

The method names and type name now look more c#y! The only difference here is the following line in the Execute method:

Bddify.CreateSentenceFromName = BddifyExtensions.CreateSentenceCamelName;

CreateSentenceFromName is a public static function on Bddify class that you can replace with whatever you like. This is how you can change the way Bddify parses your type and method names. BddifyExtensions is a very small class with two small methods:

public static class BddifyExtensions
{
    public static void Bddify(this object bddee)
    {
        Bddifier.PrintOutput = Bddifier.DefaultPrintOutput;
        var bdder = new Bddifier();
        bdder.Run(bddee);
    }

    public static string CreateSentenceFromCamelName(string name)
    {
        return Regex.Replace(name, "[a-z][A-Z]", m => m.Value[0] + " " + char.ToLower(m.Value[1]));
    }
}

Bddify is the extension method we called in the Execute method, and CreateSentenceFromCamelName is what enabled reporting off the camel cased method names. If you want some other naming for your methods, create your own reporter method and set CreateSentenceFromName to it.

Given, When, Then!

If you do not like GWT attributes and the report, it is not a problem. You can completely change the way report is created by using "extension points" on Bddify. I created few attributes for GWT which I used in the above example. You can create other attributes and use them. All you have to do is to inherit from ExecutableAttribute.

Do not like attributes?

You do not have to use attributes if you do not want to. Here is an example:

public class CardIsDisabled
{
    private Card _card;
    Atm _subject;

    void GivenTheCardIsDisabled()
    {
        _card = new Card(false);
        _subject = new Atm();
    }

    void WhenTheAccountHolderRequestsMoney()
    {
        _subject.RequestMoney(_card);
    }

    void ThenTheAtmShouldRetainTheCard()
    {
        Assert.That(_subject.CardIsRetained, Is.True);
    }

    void AndTheAtmShouldSayTheCardHasBeenRetained()
    {
        Assert.That(_subject.Message, Is.EqualTo(DisplayMessage.CardIsRetained));
    }

    [Test]
    public void Execute()
    {
        this.Bddify();
    }
}

This still provides the same result. Bddify by convention knows how to interpret your method if you method name starts with Given, When and Then.

I want to create some files from my test run

Bddify uses an action called PrintOutput that gets a string and prints it somewhere. The default implementation of the method is:

public static readonly Action<string> DefaultPrintOutput = Console.WriteLine;
public static Action<string> PrintOutput = DefaultPrintOutput;

Just create a method that gets a string and writes it to a file. Then set PrintOutput to your method. I did a very quick proof of concept just to see if this works:

public static class AnotherBddifyExtension
{
    /// <summary>
    /// This is a lame implementation of Bddify.
    /// This is just to show you that it is rather simple to implement a file reporter
    /// </summary>
    /// <param name="bddee">The class to be BDDified</param>
    /// <param name="filePath">The path to the file to be generated</param>
    public static void Bddify(this object bddee, string filePath)
    {
        using (var file = File.AppendText(filePath))
        {
            file.AutoFlush = true;

            Action<string> report =
                s =>
                {
                    Bddifier.DefaultPrintOutput(s);
                    file.WriteLine(s);
                };

            Bddifier.PrintOutput = report;
            var bdder = new Bddifier();
            bdder.Run(bddee);
        }
    }
}

If you use this extension method, as well as the report being shown on the console, a file gets created on the specified path.

Bddify is coupled with your testing framework

My implementation is coupled with NUnit in a few places. I could remove the dependency; but it would mean more code which would make this not-so-lightweight. So I decided to leave it out and do not make my simple BDD library complicated. You have the source code. Feel free to change Bddify to use your testing framework of choice.

Conclusion

That is Bddify. A very simple library that allows you to write BDD tests in a very simple manner and is capable of generating a rather decent report. It also allows you to replace some of its components which I think is handy.

Oh and I forgot to tell you. Bddifier class itself is less than 100 lines of code. With all the extensions and attributes included it is still less than 150 lines.

I still do not have any plans to make this into a real BDD library. Well, the whole point of BDD Simply is to use something very simple and lightweight that you feel comfortable using in your project.

Bddify is not currently perfect and may have some bugs. It is not as feature rich as some of the BDD frameworks out there either. If you need a feature or if you think something could be done differently or to improve it, please leave me a comment.

The source code is available on google code here.

I am aggressively updating the code and adding some features and fixing some bugs. If you are interested in the version I discussed in this article get my first check-in from google code.

Hope this helps.

testingBDDbddify
Posted by: Mehdi Khalili
Last revised: 20 Oct, 2011 01:38 PM

Trackbacks

Comments

25 Jun, 2011 04:39 PM

Hi Philip,

It does now. There is a downloadable binary for .net 3.5 on the downloads page. It does not run on earlier versions though.

23 Jun, 2011 04:01 AM

Hi Mehdi,

Does BDDify have a binary release that runs on .NET 3.5 and below?

21 Apr, 2011 05:41 AM

Hi Mehdi,

Nice work. Bddify seems like a cross between Specunit.net and StoryQ. I am a StoryQ user and the one thing I would miss is the ability to reuse the steps with different parameters.

Imagine if the atm allowed withdrawls < $10 even if the card is disabled. You would need two different steps: one for < 10 and one for > 10.

Liam.

22 Apr, 2011 02:20 AM

Liam,

Thanks for the comment. If I understood you correctly you are asking to run a step with different arguments. If yes, then bddify supports that and more:

To run a step several parameters you would need to use RunStepWithArguments attribute on the step:

[RunStepWithArgs(1)]
[RunStepWithArgs(2)]
[RunStepWithArgs(3)]
void GivenMultipleArgumentAttributesAreProvidedToSameMethod(int input)
{
}

There is no limit to the number of arguments you provide or their type. It is very much like how TestCase attribute in nunit works.

You can also run a scenario with different arguments:

[RunScenarioWithArgs(1, 2, 3)]
[RunScenarioWithArgs(-1, 5, 4)]
[RunScenarioWithArgs(3, 7, 9)] // failing test
public class FailingTestWithScenarioArguments
{
    private int _expectedResult;
    private int _input1;
    private int _input2;
    private int _actualResult;

    void RunScenarioWithArgs(int input1, int  input2, int expectedResult)
    {
        _input1 = input1;
        _input2 = input2;
        _expectedResult = expectedResult;
    }
 }

I hope this answers your concern.

Mehdi

22 Apr, 2011 12:33 PM

Liam,

There was a little bit of confusion. I thought your comment was on another post (which is titled very similarly to this one). Sorry for the confusion. What I mentioned above still applies; but not to this article.

What started as a 100 liner class was then turned into a fully fledged bdd framework with the same name. You may find more information about the framework here.

Hope this helps. Mehdi

09 Mar, 2011 09:52 PM

I really like the report output. I'm looking at doing something similar with the next version of SpecsFor (http://specsfor.codeplex.com). I also like the approach you took to "work around" NUnit's lack of flexibility. When you tag a method with the 'Then' or 'AndThen' attributes, do they still correctly count towards NUnit's pass/fail counts?

10 Mar, 2011 12:24 AM

Matt,

Bddify wraps/runs your class into/as one test. The result is a pass, ignore, or failure. The report shows you in details the result of each step of the test though. This means that you will have one nunit test per class with each class/test verifying a specific scenario. To verify the scenario you may have to write several then parts which Bddify runs; but the result is that either your scenario is implemented and passes, or it fails; i.e. you cannot have a partially working scenario.

At the time of writing this article, nothing would interrupt the execution and you would get a complete report regardless. At the time of writing this comment, the whole thing is executed unless there is an exception apart from NotImplementedException. This means that if your given, when or then fails the test stops. You have to fix the failure before Bddify runs the rest of it. This was done to avoid misleading errors on thens when a failure in given or when did not setup the state/transition properly.

HTH.

10 Mar, 2011 03:43 AM

Ah, I see. Thanks for the insight!

blog comments powered by Disqus