Addressing some Behave# concerns
This post was originally published here.
So Joe and I have received some initial feedback for Behave#. Joe’s already given a great intro into how to use Behave# and addressed Roy’s specific questions. I thought I’d address some of the common issues regarding Behave#:
- Using string matching
- Using anonymous delegates
- One developer supports it</ul>
Using string matching
One common concern I’ve heard from a couple of sources now is that Behave# uses strings to match behavior for subsequent scenarios. Something that might not be clear on how we match behavior is that Scenarios are scoped to a Story.
That is, the “context” parameter of a “Given” scenario fragment is only able to be matched against other Scenarios within a single Story. Here’s an example:
[Test] public void Withdraw_from_savings_account() {
- One developer supports it</ul>
Account savings = null; Account cash = null;
Story transferStory = new Story(“Transfer to cash account”);
transferStory .AsA(“savings account holder”) .IWant(“to transfer money from my savings account”) .SoThat(“I can get cash easily from an ATM”);
transferStory .WithScenario(“Savings account is in credit”) .Given(“my savings account balance is”, 100, delegate(int accountBalance) { savings = new Account(accountBalance); }) .And(“my cash account balance is”, 10, delegate(int accountBalance) { cash = new Account(accountBalance); }) .When(“I transfer to cash account”, 20, delegate(int transferAmount) { savings.TransferTo(cash, transferAmount); }) .Then(“my savings account balance should be”, 80, delegate(int expectedBalance) { Assert.AreEqual(expectedBalance, savings.Balance); }) .And(“my cash account balance should be”, 30, delegate(int expectedBalance) { Assert.AreEqual(expectedBalance, cash.Balance); })
.Given(<span class="str">"my savings account balance is"</span>, 400) .And(<span class="str">"my cash account balance is"</span>, 100) .When(<span class="str">"I transfer to cash account"</span>, 100) .Then(<span class="str">"my savings account balance should be"</span>, 300) .And(<span class="str">"my cash account balance should be"</span>, 200);
Story withdrawStory = new Story(“Withdraw from savings account”);
withdrawStory .AsA(“savings account holder”) .IWant(“to withdraw money from my savings account”) .SoThat(“I can pay my bills”);
withdrawStory .WithScenario(“Savings account is in credit”) .Given(“my savings account balance is”, 100); // This entry doesn’t have a match!
- Using anonymous delegates
} </pre> </div>
In the “withdrawStory”, even though the “Given” fragment string matches the “transferStory” “Given” fragments, the behavior will not match up. That’s because Scenarios belong to a Story, and the matching only happens within a given Story.
In DDD terms, the Aggregate Root is the Story, and the child Entities include the Scenarios. We _could_ match across stories, but that wouldn’t adhere to DDD guidelines, and would result in much more complexity.
So matching issues only happen within one Story. I don’t know how many Scenarios you would need to write before running into issues, but I think we could follow some of Joe’s suggestions and use some more intelligent matching algorithms.
#### Using anonymous delegates
Let me be the first to admit that anonymous delegates are clunky, difficult, and just plain ugly. But keep in mind that Behave# only deals with delegates. How the consuming test code creates these delegates does not matter to Behave#. We have several options (asterisk next to C# 3.0 features):
* [Named methods](http://msdn2.microsoft.com/en-us/library/98dc08ac(VS.80).aspx)
* [Anonymous methods](http://msdn2.microsoft.com/en-us/library/0yw3tz5k(VS.80).aspx)
* [Lambda expressions](http://msdn2.microsoft.com/en-us/library/bb397687(VS.90).aspx)*</ul>
Anonymous methods may not be very prevalent in C# 2.0, but there are still quite a few classes in the DNF that use delegate arguments for method parameters. I ran the following code against .NET 3.5 assemblies:
<div class="CodeFormatContainer">
<pre>var types = from name <span class="kwrd">in</span> assemblyNames
select Assembly.LoadWithPartialName(name) into a
from c <span class="kwrd">in</span> a.GetTypes()
<span class="kwrd">where</span> (c.IsClass || c.IsInterface) && c.IsPublic && !c.IsSubclassOf(<span class="kwrd">typeof</span>(Delegate))
select c.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static) into methods
from method <span class="kwrd">in</span> methods
<span class="kwrd">where</span> method.GetParameters().Any(pi => pi.ParameterType.IsSubclassOf(<span class="kwrd">typeof</span>(Delegate)))
&& !method.Name.StartsWith(<span class="str">"add_"</span>, StringComparison.OrdinalIgnoreCase)
&& !method.Name.StartsWith(<span class="str">"remove_"</span>, StringComparison.OrdinalIgnoreCase)
select <span class="kwrd">new</span> { TypeName = method.DeclaringType.FullName, MethodName = method.Name };
int methodCount = types.Count(); int typeCount = types.GroupBy(t => t.TypeName).Count();
Debug.WriteLine(“Method count: “ + methodCount.ToString()); Debug.WriteLine(“Type count: “ + typeCount.ToString()); </pre> </div>
And I found that there are 1019 methods with delegate parameters spread out over 155 types. With the System.Linq.Enumerable extension methods in .NET 3.5, methods with delegate parameters will be used much more often.
#### Only one developer supports it
Well…not exactly true. There are two developers, Joe and I, so that’s a 100% improvement, right? 🙂
#### Wrapping it up
I really like [Dan North’s](http://dannorth.net/) [rbehave](http://dannorth.net/2007/06/introducing-rbehave). Behave# closely matches rbehave’s usage and intent. Are a lot of the same issues regarding Behave# also valid for rbehave? Ruby lends itself well to BDD, especially when combining rbehave and [rspec](http://rspec.rubyforge.org/), with the elegance of dynamic typing and language features like blocks.
Behave# is still getting started, and we have some kinks to iron out, but I do think we’re on the right track, following in Dan North’s footsteps.