Update: More on BDD and Behave# from AgileJoe here.
AgileJoe pinged me with an email about Behave#, a Behavior-Driven-Development verification API (think NUnit syntax with a more real-world feel of behavior descriptions). Here's what a "test" might look like using this API:
1: [Test]
2: public void Transfer_to_cash_account()
3: {
4:
5: Account savings = null;
6: Account cash = null;
7:
8: Story transferStory = new Story("Transfer to cash account");
9:
10: transferStory
11: .AsA("savings account holder")
12: .IWant("to transfer money from my savings account")
13: .SoThat("I can get cash easily from an ATM");
14:
15: transferStory
16: .WithScenario("Savings account is in credit")
17:
18: .Given("my savings account balance is", 100, delegate(int accountBalance) { savings = new Account(accountBalance); })
19: .And("my cash account balance is", 10, delegate(int accountBalance) { cash = new Account(accountBalance); })
20: .When("I transfer to cash account", 20, delegate(int transferAmount) { savings.TransferTo(cash, transferAmount); })
21: .Then("my savings account balance should be", 80, delegate(int expectedBalance) { Assert.AreEqual(expectedBalance, savings.Balance); })
22: .And("my cash account balance should be", 30, delegate(int expectedBalance) { Assert.AreEqual(expectedBalance, cash.Balance); })
23:
24: //Reuse the declarations and associated actions from before
25: .Given("my savings account balance is", 400)
26: .And("my cash account balance is", 100)
27: .When("I transfer to cash account", 100)
28: .Then("my savings account balance should be", 300)
29: .And("my cash account balance should be", 200)
30:
31: transferStory
32: .WithScenario("Savings account is overdrawn")
33:
34: .Given("my savings account balance is", -20)
35: .And("my cash account balance is", 10)
36: .When("I transfer to cash account", 20)
37: .Then("my savings account balance should be", -20)
38: .And("my cash account balance should be", 10);
39:
40: }
How it seems to work:
- The initial story initialization (lines 8-14) is there to be read by the test reader and to be written to the console. It is the backdrop of this story.
- There can be several scennarios run inside a single NUnit test
- There can be several tests for a single scenario (the first scenario here has two tests)
- When you use a "Given" and "And" you also declare the associated delegate, or actions, that the code should perform. you're basically mapping a piece of test to an action. Later you can reuse that piece of text without needing to declare that action again (see lines 18 and then 25 and then 34). This is essentially creating a DSL (Domain specific language) to be used in the scenario tests.
- The full test and its output can be seen here. the output is very detailed. I'm not sure if you see it also when the tests pass or only if they fail. In any case, it's very verbose and I think should show only when failing.
My notes(and Joe's answers to them in a newly posted entry):
- Would be nice to incorporate this into other frameworks like RhinoMocks, or even to non test-related frameworks, to make them self-testing.
- This is a nice API for any behavior-driven code. I'd use it in some sort of a rules engine (some modified version of it) for basic readable rule declaration.
- You actually write your own DSL here, which is nice, but it's still developer facing. customers can't deal with this. too technical. I can't hel pbut think that this is exacly the kind of DSL that is enabled by using Fitnesse along with the DoFixture. I'm not sure duplicainog is a good thing here but I'm willing to live with the two in separate until something better that unites them comes along.
- Would be nice to have a way to declare some sort of "initalizationTest" where you place all the strings with related delgates into. Wait, there is - use [SetUp]. But still feels clunky.
- Many strings also means ability to fail in use the right string: maybe use consts but then, that would defeat the purpose... not sure what the best solution here.
- I'm not sure that using delegates is the right way to go here. Maybe using actual method references instead of anonymous delegates is better (they are utility methods anyway. Still not sure which would be more readable.
- using "then" and "And" means you can have multiple "Asserts" on a given scenario test. I think that also means you get the problem associated with using multiple asserts in a single test. I'm not sure if the frameworks helps "swallow" exceptions and moves on to the next asserts or if each exception blows up the entire test. ( so if line 21 throws an exception , will line 22 ever execute?)
- I think this syntax will be largely unreadable in VB.NET, which is much more verbose.
- Maybe there cold be a tool who would let you write these sentences in excel and then "transfer" them into c# code. that'd be awesome. especially if it was automatic. that might be the bridge to fitnesse I'm looking for.
Over all I think the API is very nice. Readable. I like how it "behaves" if you will. It is still rough around the edges, but assuming its already usable and no seious bugs, the only real possible problem may be the "multiple asserts" thing, which I'm not sure is really a problem. But what might inhibit a team fro using it?
- Developers who are already used to the NUnit syntax might dislike this syntax or "not get it"
- People might think that these tests are not instead of other unit tests - "More tests to write??"
- People may not know when to use Behave# over regular unit tests - there should be some simple guidelines on when to use which.
- Still today, many developers are not comfortable with using anonymous delegates.
- afraid to use it since its so new and only one developer supports it.
I think this is a good opportunity to start a real conversation about BDD.
- Is BDD testing just unit testing with a more "natural" syntax? Right now it seems to be.
- Should BDD separate into developer-facing and customer-facing tests? (behave# vs. fitnesse)
- If not, how do we combine these?
- it seems that BDD can be used bot for unit testing and integration testing. It blurs the lines between traditional unit testing concepts and integration tests, or, just gives some sort of unified language to do both. it's still up to us to decide what kind of test we'd like to write.
- What types of tests is BDD not good for?
- What other questions should we be asking?