The advantages of extending your shared language to your application code

2021-07-07 21-07-07
BDD
by Bas Dijkstra

Share!

 

As you probably know, one of the main benefits of adopting Behavior Driven Development as a team is the creation of a shared understanding of what the software you’re building will behave like. This involves actively discussing the intended behavior of the feature to build or bug to fix, using three amigos sessions and techniques like Specification by Example and Example Mapping. Over time, you’ll notice that, hopefully, your team starts to use a shared language to refer to specific processes, artifacts and domain entities.

This means that one of the scenarios in your executable specifications contains a step

“Given Anna has an active basic account”

everybody reading and discussing this behavior  knows:

  • What an ‘account’ is (is it a bank account? a user account? another type of account altogether?)
  • What having a ‘basic’ account means, what other account types are available and how they differ
  • What it takes for an account to be classified as ‘active’

Creating and cultivating this shared understanding and this shared language is of great value when you discuss and formulate specifications for the software you’re going to build. When there is no confusion about what terms like ‘active’, ‘basic’ and ‘account’ mean within the context of the application that you’re building, the risk of miscommunication and mismatches in expectations making their way into the code and manifesting themselves as bugs or missing features will be significantly lower.

In the automation phase of the BDD process, you’ll turn the scenarios that express the written behavior  into automated acceptance tests and executable specifications using a tool such as SpecFlow. This means that the shared language entities (for example the ‘account’ here) make their way into your test code more or less naturally. But why not take this one step further and apply it to your application as well? There are a number of ways in which the entire team could benefit from doing so:

  • Your code will be easier to understand for those that have not written it (and also for those that have written it and return to that code a couple of months later..). Think how much easier it is for developers that are new to the team and the application to understand the code if this is built using the entities and terminology that is used in the scenarios that describe its behavior .
  • It is easier to think of valuable tests at the code level. We all know that unit tests are the cheapest kind of tests in terms of execution time, and as a result, we’d like to cover as much as we can in unit tests and only test those things that cannot be automated at the unit level in a higher-level test. When your code adopts the same shared language that is spoken by your entire team, it will be easier to discuss what can (and what cannot) be tested at that level. Ultimately, this will result in faster feedback because your tests will take less time to execute.
  • If at some point a change to the business logic or the properties of a specific domain entity needs to be made, it is much easier to assess the impact this will have on your code base when your code follows the structure and language of your business-level requirements.

That sounds great, doesn’t it? But how exactly do you propagate your shared language to your application code? Let’s look at some examples of how the language in the step

“Given Anna has an active basic account”

can be used to structure your application code:

  • Have your class names reflect your business entities. Since we talk about an ‘account’, it makes sense to have a class named ‘Account’ that contains the properties and methods related to an account.
public class Account
{
public decimal Balance { get;}
public AccountType Type { get; } 
public AccountStatus Status { get; }
}
  • Use enums for properties with a specific range of values. In this step, we’re talking about an ‘active’ account. Maybe your account can also be ‘inactive’ or ‘pending’. I’d recommend you list the possible values in an enum, for example one called ‘AccountStatus’. Then, you can refer to specific account statuses in your code whenever you’re writing business logic that depends on the account status using enum values, e.g., AccountStatus.Active and AccountStatus.Pending.
public enum AccountStatus
{ 
Inactive,
Active,
Pending,
Cancelled
}
  • Use method names that reflect business-level intent or outcomes. Let’s assume that it is possible to upgrade an account to a higher level account. If you capture the actions needed to do this in a method called UpgradeAccountStatusTo(AccountType newAccountType), it will be clear to everybody reading that method name (and not just the developers!) what that method is exactly meant to do. Here’s an example of what such a method could look like as member of the previously mentioned Account class:
public void UpgradeAccountStatusTo(AccountType newAccountType)
{
// Implement the business logic that performs the upgrade here
}

Again, these are just some of the ways in which you can use your shared language not only in your specifications and your acceptance tests, but also in your application code. There are many more ways in which you can make your application code reflect your shared business language and reap the benefits listed in this article.