|

TDD vs BDD: which is the best for your next project?

Basically, TDD vs BDD is a question that every project manager needs to answer before beginning a new venture. However, both have their own benefits and drawbacks, making it difficult to decide which will yield the best results for your project. TDD offers more concrete feedback pathways and is often implemented more swiftly with greater accuracy, but it can be complex as testing each component of an application individually can be difficult. On the other hand, BDD simplifies design complexity by considering tests from the end-user perspective and by using plain language scenarios which all stakeholders can understand. Ultimately, which approach will provide the most advantage depends on the scope and scale of your project. So when comparing them, choose wisely – the right decision could make or break your next project!

Test-Driven Development (TDD)

Is a powerful tool that will help your development team stay ahead of the game! At the same time, rather than coding first, then testing after to see if it works – why not start off on a winning foot by writing tests that proactively shape and design software so you know exactly how well they’re performing.

With the TDD process, skipping out on stress doesn’t have to be an option when creating quality projects. Using it, you’ll get rid of those development stress blues and can make sure your projects always turn out great! It’s simple- don’t waste time on guesswork and start using TDD for smart solutions that help your products reach their full potential and deliver a higher quality code.

Test-Driven Development
Test-Driven Development

Behavior-driven development (BDD)

Is about collaboration between business teams and technical teams. It’s a non-technical language that makes Product Managers, Business Analysts, Quality Assurance, Developers, and Support Teams understand the application without diving too much into the technical details. Then the code is written to exhibit that behavior.

therefore Behavior-driven development (BDD) is an Agile software development methodology in which an application is documented and designed around the system’s behavior a user expects to experience when interacting with it.

This approach is often used in Acceptance Test Driven Development and Specification by Example.

TDD and BDD in team collaboration
TDD and BDD in team collaboration

The benefits of TDD

Test-driven development (TDD) is a software development process that relies on tests to drive the creation of flexible code. It has a number of benefits, both for developers and for the software itself. For developers, TDD can help to improve code quality and reduce the time spent debugging. By writing tests first, developers can ensure that their code is properly executed and meet all the requirements.

In addition, the TDD approach can help to encourage modular and scalable design. As the codebase grows, it can be difficult to make changes without breaking existing functionality. However, by following TDD principles, developers can create code that is easy to modify without introducing new bugs. Finally, TDD can also help to reduce the overall cost of development by catching errors early on in the process. For businesses, this can result in substantial savings.

In short, test-driven development is a powerful tool that can help developers to create high-quality code efficiently. When used correctly, it can benefit both developers and businesses alike.

The benefits of BDD

In essence, BDD, or behavior-driven development, is a development methodology that emphasizes collaboration between different stakeholders in order to define the application’s behavior. BDD includes techniques such as user story mapping and example-based testing in order to help developers better understand the customer’s needs. While BDD is often used in conjunction with test-driven development process (TDD), it is not limited to this approach. BDD is also a test-first approach but differs by testing the system’s actual behavior from the end-users perspective.

In fact, BDD can be used with any software development methodology. One of the key benefits of BDD is that it helps to ensure that all stakeholders are on the same page from the beginning of the project. This makes it easier to identify potential problems early on and avoid them later down the road. Another benefit is that BDD encourages developers to think about the behavior of an application before they start writing code. This helps to ensure that the code they write is actually able to meet the customer’s needs. Overall, BDD is a powerful tool that can help improve the quality of your software projects.

TDD vs BDD: What are the key differences? 

BDD and TDD may seem very similar since they are both software testing methods or strategies for a software application. In both cases, the developer writes the test before writing the code to make the test pass. And in both cases, the tests can be used as part of an automated testing framework to prevent bugs. BDD explains the behavior of an application for the end-user while TDD focuses on how functionality is implemented.

These are two popular approaches to software development.

Both TDD and BDD focus on writing tests before code, which can help to ensure that code is robust and bug-free. The TDD software testing methodology is implemented from a developer’s perspective, while BDD is from a business analysis or quality assurance perspective. The TDD test asserts the result of a specific method, while the BDD test is only concerned about the result of the higher-level scenario. So, how do you get started with TDD or BDD?

If you’re new to TDD or BDD, the best way to get started is by following a tutorial or example. There are many excellent resources available online, such as the official TDD documentation from the Agile Alliance. Once you have a basic understanding of the concepts, you can start practicing TDD or BDD on your own projects.

It’s also important to keep in mind that TDD and BDD are not silver bullets. Like any approach to software development, they have their advantages and disadvantages. For example, TDD can lead to more concise and focused code, but it can also be time-consuming. Ultimately, it’s up to you to decide whether testing BDD or TDD methodology is the right approach for your project.

Not sure what the difference is between TDD and BDD? BDD vs TDD vs ATDD : Key Differences article can be helpful!

The most common format used by the BDD community is “Given-When-Then”.

The following example is a simple structure of how it works:

Given – describes the initial context at the beginning of the scenario, in one or more clauses;

When – describes the action that the actor in the system or stakeholder performs, or the event that triggers the scenario;

Then – describes the expected outcome of that action in one or more clauses.

Let’s take one of the real-world scenarios with a clothing store example:

Title: Establishing the popularity of the product

As a store owner,

I want to see the most sold and popular products

So that could be able to create reports

Scenario: when a product record is created or updated the Is_Popular field should be marked as true

Given the create or edit form of the product record.

When a product is created or edited

And the product’s Sold_X_Times field is greater than 100

Then the product’s Is_Popular should be marked as true if the product is still available in stock.

Now let’s apply it in code.

The following example is written in Apex programming language, using syntax that looks like Java.

As per our requirement above, we have to first check if the product is popular, and we start by writing a failing test:

Testing soldXTimesShouldBeGreaterThan100 method

@IsTest
public class ProductService_Tests {
    @IsTest 
    static void soldXTimesShouldBeGreaterThan100(){
        //given
        Product2 prod = new Product2();
        prod.Sold_X_Times__c = 101;
        //when
        ProductService prodService = new ProductService();
        Boolean result = prodService.isPopular((Integer) prod.Sold_X_Times__c);
        //then
        System.assert(result == true, 'It should return true');
    }
}
Apex

Let’s run the soldXTimesShouldBeGreaterThan100() test: OOPPS! As has been noted, it fails because of course we didn’t write the ProductService() class yet and soldXTimesIsGreaterThan() method. It is ok.

Now let’s make the test pass successfully:

Writing isPopular method

public class ProductService {
  public boolean isPopular(Integer soldXTimes){
     return true;
  }
}
Apex

Run the soldXTimesShouldBeGreaterThan100() test: great job! It passed.

Now we are going to refactor the developed code to match the requirement and implement the functional code in order to make the written test case pass:

public class ProductService {
    private static  final integer POPULARITY_SCORE = 100;
    public boolean isPopular(Integer soldXTimes){
        if(soldXTimes != null && soldXTimes  > POPULARITY_SCORE)
        {
            return true;
        }
        return false;
    }
}
Apex

Run the soldXTimesShouldBeGreaterThan100() test again: awesome! It passes again.

In our ProductService_Tests class, we write another test method for testing when the product should not be marked as popular.

Testing soldXTimesShouldNotBeGreaterThan100 method

@IsTest 
  static void soldXTimesShouldNotBeGreaterThan100(){
    //given
    Product2 prod = new Product2();
    prod.Sold_X_Times__c =99;
    //when
    ProductService prodService = new ProductService();
    Boolean result = prodService.isPopular((Integer) prod.Sold_X_Times__c);
    //then
    System.assert(result == false, 'It should return false');
}
Apex

Run the soldXTimesShouldNotBeGreaterThan100() test: cool! it passes.

Now we have already the test methods written for the IsPopular() method and the logic as well. We didn’t spend time debugging, we have written exactly what we need as per the requirement. One part of our requirement is successfully done. Let’s go on:

Now we write a failing test for marking the product as popular since we already know whether it is popular or not. Here we go:

Testing itShouldMarkProductAsPopular method

@IsTest 
static void itShouldMarkProductAsPopular(){
    //given
    Product2 product = new Product2(Available_In_Stock__c = true, Is_Popular__c = false, 	Sold_X_Times__c = 101);
    //when
    ProductService prodService = new ProductService();
    Product2 markedProduct = prodService.markProductAsPopular(product);
    //then
    System.assertEquals(true, markedProduct.Is_Popular__c, 'The product should be marked as popular');
}
Apex

Let’s run it: Boom!!! The test case fails again.

Now we make it pass: In the ProductService class write the following by making the code valid:

Writing markProductAsPopular method

public Product2 markProductAsPopular(Product2 product){
    product.Is_Popular__c = true;
    return product;
}
Apex

Testing again: Yeep!!! It passes.

Now the test passes we have to Refactor the above method:

public Product2 markProductAsPopular(Product2 product){
    if(isPopular((Integer)product.Sold_X_Times__c) && product.Available_In_Stock__c)
    {
        product.Is_Popular__c = true;
    }
    return product;
}
Apex

After refactoring let’s test by running the itShouldMarkProductAsPopular() test to make sure it passes: and again – Great Job! It is working as expected.

In another testing process, the following should not mark the product as popular because Sold_X_Times__c is not greater than 100.

Testing itShouldNotMarkProductAsPopular method

@IsTest 
static void itShouldNotMarkProductAsPopular(){
    //given
    Product2 product = new Product2(Available_In_Stock__c = true, Is_Popular__c = false, Sold_X_Times__c = 0);
    //when
    ProductService prodService = new ProductService();
    Product2 markedProduct = prodService.markProductAsPopular(product);
    //then
    System.assertEquals(false, markedProduct.Is_Popular__c, 'The product should not be marked as popular');
}
Apex

And the next method also doesn’t mark the product as popular because it no more exists in stock.

@IsTest 
static void itShouldNotMarkProductAsPopularNotMoreInStock(){
    //given
    Product2 product = new Product2(Available_In_Stock__c = false, Is_Popular__c = false, Sold_X_Times__c = 101);
    //when
    ProductService prodService = new ProductService();
    Product2 markedProduct = prodService.markProductAsPopular(product);
    //then
    System.assertEquals(false, markedProduct.Is_Popular__c, 'The product should not be marked as popular. No more products');
}
Apex

Both above methods pass because the markProductAsPopular() has been already written.

Now let’s write the testConstructor() method for testing the entry point of our product records, the records that have to be processed.

Testing testConstructor

@IsTest 
static void testConstructor(){
    //given
    Product2 product = new Product2(Available_In_Stock__c = true);
    List<Product2> productList = new List<Product2>{product};
    //when
    ProductService prodService = new ProductService(productList);
    System.assert(prodService.recordsToProcess[0].Available_In_Stock__c == true);
}
Apex

Running it will definitely fail. Let’s make it pass.

In ProductService class write the following:

public List<Product2> recordsToProcess;

public ProductService(List<Product2> recordsToProcess){
    this.recordsToProcess = recordsToProcess;
}
Apex

Run again: It doesn’t work! Why? Ok, it is because we introduced a new parameter in ProductService() constructor. And it now requires the list of our product records that have to process in order to be marked as popular.

Let’s add the new List<Product2>() parameter in all the above methods where the ProductService() is instantiated and the parameter is empty:

//in ProductService_Tests class
new ProductService(new List<Product2>());
Apex

We can also add a default empty constructor in our ProductService() class but instead of that, it looks like adding parameters to test methods is the way to go. Plus, we get an extra level of protection against issues – how’s that for efficiency? I think pretty well.

Let’s run the test: a new boom!!! It is working now.

Finally, let’s write the test method to check the whole logic and if we get the record popular:

Testing recordIsPopular method

@IsTest 
static void recordIsPopular(){
     //given
     Product2 product = new Product2(Available_In_Stock__c = true, Sold_X_Times__c = 101);
     List<Product2> productList = new List<Product2>{product};
     //when
     ProductService prodService = new ProductService(productList);
     prodService.run();
     //then
     System.assert(prodService.recordsToProcess[0].Is_Popular__c, 'Product is popular, and it should return true');
}
Apex

If we run now it should fail. Ok, let’s make it pass:

Writing run method

public void run(){
  recordsToProcess[0].Is_Popular__c = true;
}
Apex

Run the test: great! It passes.

Now let’s refactor and we will make sure that all that we are doing is right.

public void run(){
  for(Product2 product : recordsToProcess){
      product = markProductAsPopular(product);
  }
}
Apex

Running the recordIsPopular() again: BOOM! It’s successfully tested.

The final versions of the code.

This is the final version of the test Class:

/* Created by Alex Furtuna */
@IsTest
public class ProductService_Tests {
    @IsTest 
    static void soldXTimesShouldBeGreaterThan100(){
        //given
        Product2 prod = new Product2();
        prod.Sold_X_Times__c = 101;
        //when
        ProductService prodService = new ProductService(new List<Product2>());
        Boolean result = prodService.isPopular((Integer) prod.Sold_X_Times__c);
        //then
        System.assert(result == true, 'It should return true');
    }

    @IsTest 
    static void soldXTimesShouldNotBeGreaterThan100(){
        //given
        Product2 prod = new Product2();
        prod.Sold_X_Times__c =99;
        //when
        ProductService prodService = new ProductService(new List<Product2>());
        Boolean result = prodService.isPopular((Integer) prod.Sold_X_Times__c);
        //then
        System.assert(result == false, 'It should return false');
    }

    @IsTest 
    static void itShouldMarkProductAsPopular(){
        //given
        Product2 product = new Product2(Available_In_Stock__c = true, Is_Popular__c = false, Sold_X_Times__c = 101);
        //when
        ProductService prodService = new ProductService(new List<Product2>());
        Product2 markedProduct = prodService.markProductAsPopular(product);
        //then
        System.assertEquals(true, markedProduct.Is_Popular__c, 'The product should be marked as popular');
    }

    @IsTest 
    static void itShouldNotMarkProductAsPopular(){
        //given
        Product2 product = new Product2(Available_In_Stock__c = true, Is_Popular__c = false, Sold_X_Times__c = 0);
        //when
        ProductService prodService = new ProductService(new List<Product2>());
        Product2 markedProduct = prodService.markProductAsPopular(product);
        //then
        System.assertEquals(false, markedProduct.Is_Popular__c, 'The product should not be marked as popular');
    }

    @IsTest 
    static void itShouldNotMarkProductAsPopularNotMoreInStock(){
        //given
        Product2 product = new Product2(Available_In_Stock__c = false, Is_Popular__c = false, Sold_X_Times__c = 101);
        //when
        ProductService prodService = new ProductService(new List<Product2>());
        Product2 markedProduct = prodService.markProductAsPopular(product);
        //then
        System.assertEquals(false, markedProduct.Is_Popular__c, 'The product should not be marked as popular. No more products');
    }

    @IsTest 
    static void testConstructor(){
        //given
        Product2 product = new Product2(Available_In_Stock__c = true);
        List<Product2> productList = new List<Product2>{product};
        //when
        ProductService prodService = new ProductService(productList);
        //then
        System.assert(prodService.recordsToProcess[0].Available_In_Stock__c == true);
    }
 
    @IsTest 
    static void recordIsPopular(){
         //given
         Product2 product = new Product2(Available_In_Stock__c = true, Sold_X_Times__c = 101);
         List<Product2> productList = new List<Product2>{product};
         //when
         ProductService prodService = new ProductService(productList);
         prodService.run();
         //then
         System.assert(prodService.recordsToProcess[0].Is_Popular__c, 'Product is popular, and it should return true');
    }
}
Apex

And this is the ProductService class:

public class ProductService {

    private static  final integer POPULARITY_SCORE = 100;
    public List<Product2> recordsToProcess;

    public ProductService(List<Product2> recordsToProcess){
        this.recordsToProcess = recordsToProcess;
    }

    public boolean isPopular(Integer soldXTimes){
        if(soldXTimes != null && soldXTimes  > POPULARITY_SCORE)
        {
            return true;
        }
        return false;
    }

   public Product2 markProductAsPopular(Product2 product){
        if(isPopular((Integer)product.Sold_X_Times__c) && product.Available_In_Stock__c)
        {
            product.Is_Popular__c = true;
        }
        return product;
   }
   
   public void run(){
        for(Product2 product : recordsToProcess)
        {
            product = markProductAsPopular(product);
        }
   }
}
Apex

It remained the run this logic when a product record is created or updated.

For this, we will use a trigger class. This is why I love the Apex Code because it is so easy to write it. Ideally, we have to create an Apex handler class for the trigger and also write test classes for the handler, but for now, we will limit our actions to the trigger class only:

trigger ProductTrigger on Product2 (before insert, before update) {

    if(Trigger.isBefore)
    {
        if(Trigger.isInsert)
        {
          new ProductService(Trigger.new).run();
        }
        if(Trigger.isUpdate)
        {
            new ProductService(Trigger.new).run();
        }
    }
}
Apex

Let’s do BDD testing

Edit for the product record
Edit for the product record

Then Save.

Record page after the record was updated.
Record page after the record was updated.

Congratulations! It is finally done. We can see that product is popular now.

My thoughts on which approach is better.

After all, this is said are we still wondering about TDD vs BDD? I think the two approaches work perfectly together. How They Work Together It’s important to note that BDD and TDD aren’t mutually exclusive — many Agile teams use TDD without using BDD. However, BDD ensures that most use cases of the application work on a higher level and provide a greater level of confidence. For example, a development team may use BDD to come up with higher-level tests that confirm an application’s behavior

Two common approaches

There are two common approaches to dealing with difficult situations: the first is to confront the problem head-on, while the second is to take a more passive approach. In my opinion, neither of these approaches is necessarily better than the other. Instead, the best course of action depends on the specific situation.

If a problem is relatively small and easily resolved, then it may make sense to simply address it directly. However, if a problem is large and complex, then a more passive approach may be necessary in order to avoid making the situation worse. Ultimately, the best way to deal with difficult situations is to remain flexible and adapt your approach as needed. By being open to both options, you will be better equipped to find the solution that works best for you.

Improve the quality

If you want to improve the quality of your code and make your life as a developer simpler, learning how to write tests is crucial. Out of the two approaches, TDD vs BDD, I believe that BDD provides more benefits. With BDD, not only do you get comprehensive test coverage, but you also gain living documentation that can be used by anyone on the team. Getting started with either approach is easy enough- all you need is a text editor and some basic programming knowledge. Examples of both TDD and BDD code are provided above. What do you think? Have you tried writing test scripts for your code before? If so, which approach did you prefer? Let me know in the comments below!

Say goodbye to the TDD vs BDD debate – it’s time for a marriage of two amazing testing strategies! Together, TDD and BDD offer unprecedented power when developing quality software applications.

If you have any questions about TDD vs BDD or just want to chat, feel free to comment below or reach out directly. I am always happy to help! Thanks for reading.

Good luck on your testing journey!

Happy Coding! 🙂

Similar Posts

One Comment

  1. I’ve had experience with both BDD and DDD, and the projects I was involved in, always started in Agile. I think that at some point in the development cycle, TDD approach works better. Thanks Alex for such a great article! The apex code examples you shared are definitely worth trying. Looking forward for the next post on the same topic :))

Leave a Reply

Your email address will not be published. Required fields are marked *