BDDfy V4

After 4 months of hard work, 30 pull requests and 300 commits BDDfy V4 is now released with a lot of cool new features. The TestStack team is super excited about this release and we hope you will be excited to see the new features too.

Examples

BDDfy now supports Cucumber-like examples on both Fluent and Reflective APIs.

This is how you can use examples in the fluent API (all of the samples are using NUnit and are available from the TestStack.BDDfy.Samples NuGet package):

public class RunExamplesWithReflectiveApi
{
    public int Start { get; set; }
    public int Eat { get; set; }
    public int Left { get; set; }

    [Test]
    public void CanRunExamplesWithReflectiveApi()
    {
        this.Given("Given I have <start> cucumbers")
                .And(_ => _.AndIStealTwoMore())
            .When(_ => _.WhenIEat__eat__Cucumbers())
            .Then(_ => _.ThenIShouldHave__left__Cucumbers())
            .WithExamples(new ExampleTable("Start", "Eat", "Left")
            {
                {12, 5, 9},
                {20, 5, 17}
            })
            .BDDfy();
    }

    // I didn't have to create this method 
    // because all it was going to do was to set Start property 
    // which is being handled by the framework
    // And the step title is provided inline
    //private void GivenIHave__start__Cucumbers()
    //{
    //}

    private void AndIStealTwoMore()
    {
        Start += 2;
    }

    private void WhenIEat__eat__Cucumbers()
    {
        // This method is called after the Eat property is set by the framework
        // I didn't have to put this here, like the Given method, but I put it here to show that 
        // you can take additional actions here if you want
    }

    private void ThenIShouldHave__left__Cucumbers()
    {
        Assert.That(Start - Eat, Is.EqualTo(Left));
    }
}

Here is the generated console report:

Example Console Report

and the HTML report:

Example HTML Report

You could also write examples in the reflective API:

public class UseExamplesWithReflectiveApi
{
    private int _start;
    private int _eat;

    [Test]
    public void CanRunExamplesWithReflectiveApi()
    {
        this.WithExamples(new ExampleTable("Start", "Eat", "Left")
            {
                {12, 5, 7},
                {20, 5, 15}
            })
            .BDDfy();
    }

    void GivenThereAre__start__Cucumbers(int start)
    {
        // the start argument is provided by the framework based on the example entries
        // please note that `start` argument name matches the `start` header from the examples
        // and also it has to match with the <start> placeholder in the step title which is created based on conventions
        _start = start;
    }

    // I can still override the step type using the `Executable` attributes
    [AndGiven("And I eat <eat> of them")]
    void WhenIEatAFewCucumbers(int eat)
    {
        // the eat argument is provided by the framework based on the example entries
        // please note that `eat` argument name matches the `start` header from the examples
        // and also it has to match the <eat> placeholder in the step title
        _eat = eat;
    }

    void ThenIShouldHave__left__Cucumbers(int left)
    {
        // like given and when steps left is provided here because it matches the example header and is found on the step title
        Assert.That(_start - _eat, Is.EqualTo(left));
    }
}

For more information on the example support refer to this post. We would not have examples support in V4 without Jake's huge contribution.

Improvements on Story

Before V4 Story was relatively rigid. We had one Story attribute with As a, I want, so that syntax and it would show up with Story prefix in the reports. These limitations have now been lifted.

StoryNarrative

Story is now a subclass of StoryNarrative class:

public class StoryNarrativeAttribute : Attribute
{
    public string Title { get; set; }
    public string TitlePrefix { get; set; }
    public string Narrative1 { get; set; }
    public string Narrative2 { get; set; }
    public string Narrative3 { get; set; }

    // rest of class removed for brevity
}

There are a few things of note:

  • There is a TitlePrefix property that allows you to specify what prefix should be used for "stories" in the reports. For example if you prefer the word Specification over Story you can create a new SpecificationAttribute that subclasses StoryNarrative attribute where TitlePrefix is set to Specification (or you could provide your desired prefix for each story if you wanted to).
  • StoryNarrative doesn't make any assumptions around story narrative format. It only knows there could be up to three lines in the story narrative. So you could create your custom attribute to tell the story in a different way if you want; e.g.
public class InOrderToStoryAttribute : StoryNarrativeAttribute
{
    public string InOrderTo
    {
        get { return Narrative1; }
        set { Narrative1 = value; }
    }

    public string AsA
    {
        get { return Narrative2; }
        set { Narrative2 = value; }
    }

    public string IWant
    {
        get { return Narrative3; }
        set { Narrative3 = value; }
    }
}

And use it instead of the built-in Story attribute:

[InOrderToStory(
    InOrderTo = "In order to do BDD properly",
    AsA = "As a great programmer",
    IWant = "I want to use BDDfy")]
public class GreatProgrammersUseBddfy

This attribute is not part of BDDfy core at the moment. In order to syntax has gained a fair bit of traction in the BDD community and we would like to have it in the core but the name, InOrderToStoryAttribute, is a bit awkward. We could alternatively add the InOrderTo property to the Story attribute. We need your feedback on this.

Also StoryMetadata class now only relies on narrative lines; so you can completely skip over StoryNarrative attribute and inject the StoryMetadata directly based on some completely different means. You might find more information around this extension point here.

Narrative convention

Starting V4 you can leave out the As a, I want and So that from your story narratives. So instead of:

[Story(
    AsA = "As a great programmer",
    IWant = "I want to use BDDfy",
    SoThat =  "So that I can do BDD properly")]

you can now use

[Story(
    AsA = "great programmer",
    IWant = "to use BDDfy",
    SoThat =  "I can do BDD properly")]

And BDDfy will inject the missing text for you. This is backward compatible and only injects the text if it's missing.

We have applied the same logic to the Fluent API step titles (more on this shortly).

Complex flows in Fluent API

A couple of years ago I thought complex flows are anti-pattern in BDD and UI testing which is why you couldn't write a when or given after a then. Well, I was wrong. Now I believe complex flow is a critical part of Acceptance Test Driven Development. So we've added support for complex flows in Fluent API. Here is an example:

this.Given(x => x.IAmOnHomePage())
    .When(x => x.ILogin())
    .Then(x => x.IShouldBeOnTheHomePage())
        .And(x => x.IShouldBeAuthenticated())
    .When(x => x.ILogOut())
    .Then(x => x.IShouldReturnToHomePage())
        .And(x => x.IShouldNoLongerBeAuthenticated())
    .BDDfy();

The Fluent API has completely opened up to allow for any combination of steps. This feature is currently missing from the reflective API mostly because applying the order in the reflective API is a bit more complex as it only relies on reflection. Don't worry though. We have a few ideas for adding that to reflective API and hopefully that'll surface in the near future.

More goodness on Fluent API

  • As mentioned above, step type is now prepended to step title if you don't specify it. For example .Given(_ => Foo()) will report as Given foo. This allows for easy code reuse where for example you can use Foo as Given and When. This change is backward compatible so your .Given(_ => GivenFoo()) will still work as before.
  • Fluent API now also supports inline steps which means you can just use an anonymous function inline with the step definition; e.g. .Given(_ => { // some code in here // }, "Some title"). Since BDDfy can't resolve the step title from the anonymous function, you have to pass the title in as the second argument.
  • We also support title-only steps in Fluent API; e.g. .When(_ => "something happens"). This is quite handy in situations where you don't want to execute a method but just want to communicate something on the report.
  • We now also support deep method calls on Fluent API so you could write .Given(_ => someClass.SomeOtherClass.SomeMethod()). Another cool little feature in Fluent API is the introduction of StepTitle attribute. You could decorate your step methods with StepTitle and just call the method from the step definition API and let BDDfy extract the title from the attribute. This combined with deep method calls allows you to delegate the step title generation to methods on classes defined out of your scenarios. If you are for example using page objects for UI automation, you could put the step title on your page actions! This violates SRP but nevertheless is very cool and it cuts down on the amount of code you have to write in your scenarios.
  • Step arguments are now evaluated lazily in Fluent API so if previous steps cause the value to change, the value at the time of step execution will be reported in the step.
  • Step arguments can now also be method calls. BDDfy will try to execute the method and inject the return value as the step argument; e.g. .Given(_ => SomethingIsSet(SomeArgument(), AnotherArgument())). We also added support for passing local variables as arguments to steps.

Metro-stype HTML report

There is now a new metro-style HTML report for metro lovers. This is not active by default and if you want to use it you have to tell BDDfy: Configurator.BatchProcessors.HtmlMetroReport.Enable();

Metro Report

Michael has written an extensive post about this here.

Self-contained HTML reports

Both HTML reports are now self contained (this was actually shipped in 3.19). This means you will have one file, instead of 3+ files that you had before, that you can easily share from your artifact repository on your CI server, DropBox or just email around. Your custom JavaScript and CSS files are also embedded in the report to ensure there will be one and only one file in the output.

"But what about jQuery?", you ask! We didn't want to embed jQuery into the report but at the same time wanted to have a self contained HTML report. So we are resolving jQuery from jQuery CDN. We have also added a ResolveJqueryFromCdn config point, set to true by default. This allows you to embed jQuery into your report if you need to view the reports without an internet connection.

Also worth noting that since everything is being embedded into the HTML report we decided to minify the bddfy.css and bddfy.js files to make the HTML report smaller and the HTML source more readable.

Tags

You can now add Tags to your scenarios. Here is what our example test in reflective API would look like with tags:

this.WithExamples(
    new ExampleTable("Start", "Eat", "Left") {
                    {12     , 5    , 7     },
                    {20     , 5    , 15    }})
    .WithTags("Tag1", "Tag2")
    .BDDfy();

The tags as reported in the console report:

Tags in console report

and the HTML report:

Tags in HTML report

We will later add filtering based on tags to the HTML reports.

But steps

For full compatibility with Gherkin we now support But steps. But steps are more or less like And steps except that they read as, ermmmm, but!

There is a new ButAttribute for those who love ExecutableAttributes, a naming convention for methods starting with But (ButGiven, ButWhen, ButThen and But), and step method overloads in the Fluent API.

Breaking Changes

It wouldn't be a major edition without some breaking changes and we have a few; but all for very good reasons. So here we go:

  • Some long namespaces were removed from the framework so the API becomes more discoverable. This is a rather big breaking change particularly for fluent API users; but also relatively easy to fix. You just need to delete the now-removed namespaces from your using statements. Most of the API is now located directly on the TestStack.BDDfy root namespace.
  • The Reporters namespaces that you would use to configure BDDfy's reports has been moved to the root namespace too.
  • We have renamed a number of types to more accurately reflect their role and usage:
    • renamed ExecutionStep to Step & Step's StepTitle to Title
    • renamed StepExecutionResult to Result as it was used for Scenario and Story too.
    • renamed StepAction in Step to Action
    • renamed MetaData to Metadata everywhere
  • .Net 3.5 support has been discontinuted in V4. So if you want to feel all the love that's coming to V4 you will need to upgrade to .Net 4+.
  • Strong naming has been removed. Please don't leave me any comments around your need for strong naming. Stop strong naming your assemblies. Who would strong name a test assembly (apart from the organization for whom I created BDDfy in the beginning)?! It is a bad idea.
  • Story.Category has been removed in favour of scenario Tags. This shouldn't impact anyone as Category was never fully implemented.

Update to V4

V4 has a lot of goodness in it and we are very excited about it. This is the best thing that's happened to BDD in .Net since BDDfy itself :) Go and update your BDDfy package to V4 and enjoy the new features.

comments powered by Disqus
Google