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:
and the 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 by Jake Ginnivan. We would not have examples support without his 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 wordSpecification
overStory
you can create a newSpecificationAttribute
that subclassesStoryNarrative
attribute whereTitlePrefix
is set toSpecification
(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 asGiven foo
. This allows for easy code reuse where for example you can useFoo
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 ofStepTitle
attribute. You could decorate your step methods withStepTitle
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();
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:
and the 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
toStep
&Step
’sStepTitle
toTitle
- renamed
StepExecutionResult
toResult
as it was used forScenario
andStory
too. - renamed
StepAction
inStep
toAction
- renamed
MetaData
toMetadata
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 scenarioTags
. This shouldn’t impact anyone asCategory
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.