Behavior Driven Development & Testing

Test Driven means great!

The aim of Test-Driven Development (TDD) is to minimize the number of mistakes in the code by forcing the developer to analyze the test cases before writing the code. Its main value comes from engaging the programmer in considering all the use cases before writing the production code.
The traditional process of writing code using TDD involves following steps:
  1. Plan what you want to do! (What’s given? What’s the result?)
  2. Write your test! (Arrange/Act/Assert)
  3. Realize the test fails…
  4. Make it green (by writing your code)
  5. Then follow the aforementioned steps until your functionality is implemented.
The obvious drawback is that it takes time to think of the test cases, so the implementation process is slower than writing the same code without TDD, which again is slower than writing the same code without writing any tests, which is again slower than not testing your code at all… There is a pattern to be seen there.

It may be considered a heresy by some of the TDD enthusiasts, but I’m convinced that implementation with TDD will always be slower than one without it (that’s why people consider not using it). There’s an investment one makes in hopes of writing more maintainable and stable code.

But…

I feel that Test Driven Development is a tool designed by programmers for programmers. By that I mean that its purpose lies fully in maintaining code readability and quality. It prevents you from making mistakes and from not considering all the technical cases. It doesn’t prevent you, however, from writing working code that does wrong things. Having said that, proving the validity of your understanding of the requirements is not the goal of TDD, so it fully accomplishes its purpose (It’s still great!).

Let’s assume our team of developers got the following feature to be implemented:
As a client of a bank, I want to be able to send an online bank transfer to another account.

For many of you the first reaction probably was “well, that’s not very precise…” or “ok, but what if…?”. And that’s perfectly valid! Such questions lead to discussions which can be resolved (and often are) by writing down the agreement in natural language, and later attaching it to our requirements. This discussion goes on until all the questions are answered and the answers are written down. After that, our feature is ready to be implemented.

…the monster rears its ugly head…

Unluckily this part comes usually during implementation of said feature or, in the best case, during some kind of detailed planning (in Scrum that would be Task Planning). After the developer started the implementation, the realization comes: the natural language doesn’t cover all the cases and it’s often unclear what’s the expected outcome in a particular case. It’s already a problem, but he can always reach the Project Manager/Product Owner and simply get the answers. What could be worse, is if it’s unclear what cases are actually covered. And if that situation occurs multiple times in one feature, it can significantly slow the process down.

In our example we can have a case defined as follows:
  • If there’s money on the sender’s account, it will be transferred to the recipient’s account.
  • If there’s not enough money on the sender’s account, it won’t be transferred to the recipient’s account.
That particular example being fairly straightforward, you probably have already in mind few problematic edge cases. But did you find them all (you could write them down now)?

Behavior Driven Development (BDD)

This part could also be called “There’s no universal cure”. There are, however, methods that allow us to minimize the probability of making a mistake. If you use TDD, you probably saw technical bugs occurring despite using this methodic. It won’t fix all the problems, but it will help you avoid them.

Now that we got the elephant out of the room, let’s get to the meaty part.

BDD is in fact not so different from TDD, it just operates on a different level. While TDD is supposed to help you avoid technical mistakes and write clean code, BDD aims to support writing clean requirements and avoid misunderstandings.

The method focuses on defining clear business cases which will act as a basis for a discussion on requirements. That means, unluckily, that we still have to do all the work, but this time we’re hoping it will go just a bit easier. Let’s follow the same pattern, as we do while writing unit tests (Arrange/Act/Assert):

(I kept the key words in the example bolded)

Given following Bank Accounts

Account Number
Saldo                
FR12345                         
500 EUR
DE54321                         
400 EUR

When the following Transfer is requested between the following Bank Accounts
Sender’s Account Number
Recipient’s Account Number
Transfer Amount
FR12345
DE54321
300 EUR



Then the following Bank Accounts have the following Saldo
Account Number
Saldo
FR12345
200 EUR
DE54321
700 EUR

Such written requirement won’t solve all your problems, but it probably will help you think about business cases before implementing the functionality. One use case can help you think about many questions:
  • What if the Transfer Amount is bigger than Saldo? What if the Transfer Amount is negative? What if Saldo is negative?
  • What if the recipient and the sender is the same Bank Account? What if one of them is an invalid account number?
  • What if I request multiple transactions?
  • What if the transfer is unsuccessful? (look at the “transfer is requested”)
  • What if two Bank Accounts are in different currency? What if there a difference between exchange rate at the time of request and execution of the transfer?
What if…

I’m pretty sure I didn’t list all the possible questions here, and those are only the questions from a developer’s point of view – by that I mean we don’t question the validity of the business case.
And what if we question its validity? A business analyst may ask what happens, if there’s not enough cash on the account, but there’s an open credit line. What if the transfer has been requested, but it got cancelled before realization? What if the recipient’s bank rejected the transaction for an unexpected reason? What if…

The shown method is called Specification by Example and it’s the first and very important step of Behavior Driven Development. Writing such tests is usually a teamwork between the client, business analysts and technical specialists. Of course, to write useful tests, the whole group needs to have a common understanding of terms and phrases used in the test (what do we understand under the term “account”? What’s a “transfer”? When is it “requested”?). To provide a common understanding, one must define a Domain Language. However, this topic, as well as connected to it Domain Driven Design (DDD), is enough to be covered in a separate article.

Next steps

Well defined test cases can be later used as Living Documentation. That means that they can be provided to the development team and used as a document between the team and the client. Not only do they serve as clear requirements for the project and precisely state what’s to be implemented, in such form they can also serve as Integration Tests and Acceptance Tests.

The proposed form of the test is not accidental and can be actually written and automatized, using 
Gherkins language to define the test cases and a library (specific to the programming language the team is using) to automatize them.

Conclusion

Practicing development using Specification by Example and Behavioral Driven Development helps to realize all the possibilities and helps ask the right questions. It’s like exploring a new land – if there are no signs, you still can find all the places and create a great map of the territory. It’s just a little easier when there are signposts, listing all the possible directions on the crossings.

Comments

  1. good article..surely makes my concept clear about TDD and BDD

    ReplyDelete
    Replies
    1. Thank you! I'm glad you find the article helpful :)

      Delete

Post a Comment

Popular posts from this blog

Refactor or not to refactor – is that even a question?

By failing to prepare you’re preparing to fail