Behavior-Driven Development (Cont.)
It’s About Design
Like Test-Driven Development, Behavior-Driven Development is concerned with software design. As with TDD, it leverages friction found in testing to indicate the presence of subtle design problems that will cause downstream software production throughput problems, causing the code and the system itself to be harder to work with each time new code is integrated into the whole.
TDD is often specifically about the design of code units and modules such as classes. BDD is also concerned with unit design, but its focus addresses a much broader range of design concerns.
BDD is used in support not only of unit design, but also in support of shared language design, test design, and even user experience design.
Test-Driven Development could more accurately be called Client-Driven Development. The practice encourages unit design to be done from its API first. Rather than conceive of an API design in some kind of modeling tool, Client-Driven Design practices force the designer (developer) to prove the value of the APIs design by experiencing it first. Client-Driven practices like TDD act as a kind of user experience testing for APIs, classes, and other modules.
If you’ve ever worked with an API that doesn’t seem to make any sense to you as a developer, then it’s quite possible that the API was designed using assumption-driven rather than test-driven development. All the best assumptions in the world might not amount to much when it comes down to using that API in real projects.
Often, APIs built using TDD are easier to work with and that APIs resulting from assumption-driven development cause more unnecessary friction, making it harder to achieve the level of feedback that drives lean development, and reduces the overall quality of the product and the development process.
An API is assumption-driven when it isn’t generated from examples of code that demonstrate how a developer would ideally use the API.
When you’re using client-driven development to build an API, you first write down examples that present the API in context with how it might be used, showing the API at its best based on how you wish it to be if you were in fact its designer-which you in fact are. So make the API you wish you had without any restrictions on your drive to make the API that brings ease to development, and use any lack of ease in getting the example code to work to tell you that the design has flaws.
When this client code feels right, generate the API code using a refactoring tool and incrementally fill in the implementation, continuing to run the example code as you go, immersed in the feedback that comes from seeing the code work or fail.
You could use any old VB or C# Visual Studio project to write down these snippets of client code, like a console project, or a Windows Forms project. It’s usually much easier just to use one of the readily-available unit testing tools like NUnit to capture these API examples and to run them. Testing tools provide ways to select the code to run, they provide results of the execution of the examples through Windows user interfaces and IDE integration, and they provide APIs of their own to help determine that the examples do indeed work correctly.
If you design the API with TDD, you do so from the perspective of code that acts as a client to the API. This way, the designer (developer) puts himself in a position to be a user of an API, and then designs the user experience of the API so that it reflect his needs and his ideas of a good and sensible experience.
You wouldn’t think of designing any kind of software interface-user interface or other-from a position where you could not get immediate feedback from the client. If you did, you’d just be building software based on your assumptions of what the client wants, rather than putting him in front of the software and having him tell you if it feels right or wrong.
Assumption-Driven Development (ADD) is a frequent symptom of phased-based development processes, and processes that attempt to accomplish detailed API design using diagramming tools like Visual Studio’s class designer and extremely abstract languages like UML. ADD causes waste by decreasing accuracy and increasing rework.
APIs and the classes, interfaces, and modules invariably end up reflecting more desirable software design qualities when designed from the client perspective. Client-Driven Design produces code that is more loosely coupled and more cohesive, and pays greater respect to design-first principles than typical ADD code.
Code that reflects first principles is easier to understand, easier to work with, and typically offers greater opportunities for reuse.
Folks who use TDD do so because it exercises the code’s design as the first act and first priority of implementation. Practitioners of TDD use testing frameworks when creating the client code because they provide code runners, which eliminates the need to write custom driver code for their client code.
A New Name
With all this talk about testing and bandying about the word test so much, folks got the idea that Test-Driven Development was all about testing.
Folks who do TDD for a while typically come to the conclusion that the only thing that TDD has to do with testing is the appearance of the word test in its name. That’s a bit of an exaggeration, but it’s a common one used by TDD’ers to help clarify the issue.
We use unit testing frameworks to do TDD, and we write the example code in methods that can be executed by the test runners that come with unit testing frameworks, but that’s about the end of the commonalities between TDD and testing.
If you don’t write your tests first in order to surface and weed out design friction and create a positive user experience for your API, you’re not really doing Test-Driven Development. You may have every .NET unit testing framework under the sun plugged into your solution, and you might write unit tests until the cows come home, but merely the presence of unit testing in your project isn’t an indicator that you’re engaged in client-driven practices like TDD in support of lean software production. But simply writing example code first to flesh out the highest quality, production-optimizing changes everything.
To be fair, the resulting code that TDD practice leaves behind is stored with the application code and is used to verify the correct behavior of the system through its lifetime. And this is a bunch of fancy words that mean testing. So, you get testing out of TDD after all, but it comes largely as a side effect of doing Client-Driven Development armed with a unit testing framework.
TDD’s essential goal is to design and specify the behaviors of the system according to the expectations of the customer, and to be able to consistently prove that those behaviors work according to those expectations for as long as the product lives.
In an effort to clear up the misconceptions that come from the name Test-Driven Development, and to shine some light on understandings that were surfacing from years of practices of TDD, Dan North coined the term Behavior-Driven Development to more accurately frame the practice.
Ultimately, anything that could be thought of as BDD could have been done with plain old TDD, and in fact, it often was-for folks who hadn’t been misled by the confusion that the word test had caused. The rest of us spun our wheels for years, struggling, as Dan North puts it, wanting to know where to start, what to test and what not to test, how much to test in one go, what to name tests, and how to understand why a test fails.
What systems do is a greater concern than the data that gets done to. Even when we’re writing data access components, we’re concerned with the behavior of the system rather than the data itself. A bug will happen because of problems with the system’s behavior. That bug might manifest itself as incorrect data, but it’s the system’s behavior that is causing the data to be incorrect.
Business objects are behavioral objects. They likely hold on to data, but the data is there to support the things that the business can do with it through the behaviors of the system.
We need to get the behaviors right, and we need to describe them so that other developers who work with the code can gain an understanding of how things work and what things are.
As developers, we do behavior specification of the system’s implementation using code, but before we get to the code, the customer is doing behavior specification with user stories.
User stories are the central axis around which a software project rotates. Developers use user stories to capture requirements and to express customer expectations. User stories provide the unit of effort that project management uses to plan and to track progress. Estimations are made against user stories, and user stories are where software design begins. User stories help to shape a system’s usability and user experience.
User stories express requirements in terms of The Role, The Goal, and The Motivation.
A user story isn’t concerned with a named user, like “Bob Smith” or “Anne Wilson.” User stories are concerned with a user role, for example: a customer, or a salesman, or a call center operator.
User stories describe what that user wants to do from the perspective of an interaction with a business process, like purchasing, or ordering, or paying a bill. A user story describes the user’s goal in terms of needing to get something done with the business, rather than in terms of using the system itself.
A user story also captures the user’s motivation for wanting to accomplish the goal. The motivation captures some of the back-story and the context for the user’s interaction with the business.
The back-story provides clues that can guide further analysis of the application with subject matter experts and business analysts.
A story’s motivation statement provides some insight into a user’s reasonable expectation for how the feature or function that satisfies the story may work. Without stating the motivation, a lot is left to interpretation by the implementers, and the customer may simply not have his expectations met. A missed expectation is waste and leads to rework.
A story’s motivation focuses the aim of the story, helps all the stories of the system fit together and fit with users’ expectations, and provides the extra context that helps us consider what a good user experience might be.
Without stating the user’s motivation for the story, the implementers simply may not have a clear understanding of what the customer was really trying to accomplish and how he needed to do so. A story without a statement of the user’s motivation is often thought to be unactionable. That is, the story can’t be easily estimated or implemented.
A story’s motivation can be optionally omitted from a story that isn’t going to be immediately implemented. If you place a story in the project backlog rather than the iteration backlog, then you might omit the motivation for the time being if the motivation isn’t clear to you. When a story moves into an iteration backlog, you should make all reasonable efforts to surface the user’s motivation.