Browse DevX
Sign up for e-mail newsletters from DevX


Achieve the Best of Two Worlds with Behavior-Driven Development : Page 5

Both Extreme Programming and Scrum are more effective when practiced together—and even more effective when combined to form Behavior-Driven Development.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Another Example
Implementing the specification that describes the constraint on account equality for funds transfer forces me to implement the equality constraints for the accounts. Rather than open that can of worms right off the bat, I'm going to choose to implement the Cannot transfer an amount greater than the balance of the "from" account specification:

[Concern("Funds transfer")] public class when_transfering_an_amount_greater_than _the_balance_of_the_from_account : ContextSpecification { private Account _fromAccount; private Account _toAccount; protected override void Context() { _fromAccount = new Account { Balance = 1m }; _toAccount = new Account { Balance = 1m }; } [Observation] public void should_not_allow_the_transfer() { typeof(Exception).ShouldBeThrownBy(delegate { _fromAccount.Transfer(2m, _toAccount); }); } }

Note that I didn't write this observation to say that the code should raise an error or throw an exception. That fact is secondary to the fact that the transfer is disallowed. If I want to know the implementation details of this constraint, I'll read the implementation code of the specification rather than its description as captured by the observation text.

Figure 2. Inadequate Funds: Here's the specification to deal with inadequate funds.
Figure 2 shows the updated specification report, including this new specification.

The preceding code example used the ShouldBeThrownBy() method in place of NUnit's traditional ExpectedExceptionAttribute on the test method. The ExpectedExceptionAttribute causes the specification to be expressed in two locations for the same method: first, in the body of the method, and second, above the method declaration where the attribute is added.

The SpecUnit .NET binaries include a set of extension methods that allow for a more literate style of expressing expectations and assertions. The ShouldEqual() method used in the first example, and the ShouldBeThrownBy() method are both extension methods provided by SpecUnit .NET.

Reuse: Friend or Foe
The example contexts have some shared code. A programmer's first instinct is likely to refactor this shared code into a common base class—but this is usually the last thing you want to do with specification code!

Specification code is intended to document the behaviors of the system, and this often means leaving duplicated code in place to support the "learnability" of the specs, and the readiness of the concepts and notions communicated by the specs.

If you do move common context code to a base class, do so with care, with full knowledge of how you're impacting the learning experience of the code. People are often called upon to read he code written by others on their teams, or code written by people who have since moved on—especially on agile teams where collective ownership of the code by the whole team is the rule.

The goal is to create the best possible learning experience for people who are called upon to learn from your code and to understand it—possibly for the first time! Scattering a specification's code across multiple context class definitions just for the sake of reuse isn't considered good form in writing specification code (or plain old unit test code, for that matter).

Figure 3. Common Code: Here's the common code represented in a specification.
Nonetheless, I've included an example (see Listing 1) that shows how to represent the common code as a common base class.

The previous example uses a base class to capture the shared code. I changed both specification classes to extend this class rather than the ContextSpecification class.

Figure 3 shows the specification report, updated to convey the shared context.

Again, use your best judgment when trying to achieve reuse in specification or test code. Reuse is often considered an anti-pattern in this context as opposed to in application code, where reuse is often considered desirable.

A Few Changes for Clarity
The exception-handling code in the second context class is not as clear as it could be. It would be better to get the code that represents the observation down to a single line so that the code maps more directly to the observation text. The following code makes this happen:

[Concern("Funds transfer")] public class when_transfering_an_amount_greater_than _the_balance_of_the_from_account : behaves_like_context_with_from_account_and_to_account { private Exception _exception; protected override void Because() { _exception = ((MethodThatThrows) delegate { _fromAccount.Transfer(2m, _toAccount); }) .GetException(); } [Observation] public void should_not_allow_the_transfer() { _exception.ShouldNotBeNull(); } }

In the preceding example moves the cause of the observation being made from the specification method into the Because() method. Depending on your preference, you might not find this any clearer—or you might even find it less clear.

With the specification in this form, you can add a clarifying observation about the exception, and communicate more appropriate information through the specification report:

[Observation] public void should_raise_System_Exception() { _exception.ShouldBe(typeof(Exception)); }

Figure 4. Clarifying Observation: The figure shows the specification after adding a clarifying observation about the exception.
Figure 4 shows the updated specification report.

Full Circle
With the specification report in hand, the development team can take a step back and check their thinking against the specifications and criteria given by the customer.

As the development team writes code that intends to deliver on a customer's expectations, the team might add new implementation-level criteria that might best be vetted by the customer, architect, product designer, or another developer.

The specification report is also an ideal document to give to testers to describe in plain language the expected behaviors of the system. They can use the specification report to understand what the system does and what they need to test.

The report is a valuable design review tool as well. It has been a constant source of surprise (and pleasure) to me to see developers review the specification report for code that they have just written and find tactical design flaws and misinterpretations of requirements!

A good deal of the effectiveness of the specification report comes from using a Context/Specification style for writing tests. It would be quite difficult to surface this kind of documentation from traditional unit tests that don't document individual contexts, but merely collect sets of seemingly unrelated tests for a given application class into a given test class.

The Context/Specification style has also proven to be an effective style for writing specifications that are easier to understand and can be understood much more quickly. This supports the navigability of the code base, brings yet more agility to the experience of the code, and eliminates yet more waste in the effort to develop, test, and maintain the system.

Whole Practice
The benefits of user story practices and Test-Driven Development practices have been key elements in agile software development using Scrum and XP. The entire concerted effort toward lean software production would be quite hampered if Behavior-Driven Development were merely practiced as a novel unit-testing pattern.

Agile development methodologies' strength are in the concerted and orchestrated employment of its many practices, intended to complement and strengthen each other, and to strengthen and unify diverse and cross-functional teams.

You might choose to only use the Context/Specification style for writing unit tests, and you would inevitably benefit from it, but Behavior-Driven Development is a whole practice that has tremendous positive effects on a software production effort, and the software team when practiced as a whole.

Behavior-Driven Development, like XP and Scrum, is more than the sum of its parts.

Final Thought
I left the last remaining specification from the example as an exercise for the reader. If you take a stab at it and post it on your blog or elsewhere, please send me a link.

Scott Bellwareis a senior software designer at Dovetail Software in Austin, TX. He is a Certified ScrumMaster (CSM) and a Microsoft MVP for C#. Scott teaches agile engineering practices through workshops held in conjunction with .NET user groups in the US and Canada. Scott is the track chair for the agile development track of the DevTeach conference in Montreal, and the Innotech conference in Austin. He is a member of the CodeBetter community of software bloggers.
Thanks for your registration, follow us on our social networks to keep up-to-date