DEV Community

Cover image for Why Test Driven Development
mosbat
mosbat

Posted on

Why Test Driven Development

Many Devs I worked with in the past, do not like writing the unit tests in advance. They would rather finish writing the function quickly and worry about testing later.

I only learned the hard way when working on my hobby projects, why TDD is a game changer.

Writing unit tests or integration tests, not only help you spot bugs early on; but it also enables you to strictly follow the acceptance criteria for the features or assert the expected behavior in case you're fixing a bug.

More often than not, the requirements aren't fully clear at the beginning of the project or the beginning phases. As you go further during the development process, changes happen very quickly; but you don't want your functions to behave as they please, do you?

When you write the unit tests, it helps you understand what changes need to be made without having to rewrite everything from scratch. This doesn't mean that the unit tests will have edge cases that aren't well covered. You'd still have to figure out what are the edge cases and add unit tests for them once you've confirmed the expected behavior of your application for those specific edge cases.

Just to inspire you, I got a simple scenario from ChatGPT on how notorious it is to catch bugs without unit tests:

Imagine we have the following function:

public class ShoppingCart {
    public double calculateTotalPrice(double[] prices, double discountThreshold, double discount) {
        double total = 0.0;

        // Sum up the prices
        for (double price : prices) {
            total += price;
        }

        // Apply discount if total is above threshold
        if (total > discountThreshold) {
            total -= total * discount;
        }

        return total;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now imagine we were asked to calculate the discount based on the rounded total instead of total we had in the original function:


public class ShoppingCart {
    public double calculateTotalPrice(double[] prices, double discountThreshold, double discount) {
        double total = 0.0;

        // Sum up the prices
        for (double price : prices) {
            total += price;
        }

        // Apply discount based on rounded total
        if (total > discountThreshold) {
            total = Math.round(total * 100.0) / 100.0;  // Round to 2 decimal places before applying discount
            total -= total * discount;
        }

        return total;
    }
}
Enter fullscreen mode Exit fullscreen mode

Even though the rounding change might have seemed small, it introduces several edge cases that we didn't account for, such as the situation when we are rounding values that won't hit 100.

From the initial phase, everything seems fine; but if you run this in production, you'll begin getting complains pretty soon!

Those errors or problems can be caught early on if you had some unit tests:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class ShoppingCartTest {

    @Test
    public void testCalculateTotalPriceWithDiscount() {
        ShoppingCart cart = new ShoppingCart();

        // Test case where the discount should be applied
        double[] prices = {50.0, 60.0};
        double discountThreshold = 100.0;
        double discount = 0.1;  // 10% discount

        double expected = (50.0 + 60.0) - (50.0 + 60.0) * 0.1;
        double actual = cart.calculateTotalPrice(prices, discountThreshold, discount);

        assertEquals(expected, actual, 0.01);  // Tolerance for floating-point comparisons
    }

    @Test
    public void testCalculateTotalPriceNoDiscount() {
        ShoppingCart cart = new ShoppingCart();

        // Test case where the discount should NOT be applied
        double[] prices = {30.0, 40.0};
        double discountThreshold = 100.0;
        double discount = 0.1;

        double expected = 30.0 + 40.0;  // No discount applied
        double actual = cart.calculateTotalPrice(prices, discountThreshold, discount);

        assertEquals(expected, actual, 0.01);  // Tolerance for floating-point comparisons
    }
}

Enter fullscreen mode Exit fullscreen mode

As your application keeps growing, you'll run into more edge cases depending on the complexity of the application and components' dependencies on each other.

To guarantee that your application is running as intended, you'd have to always work hard on writing as many unit tests as possible to catch errors or problems early on.

Worst case scenario, a unit test fails after making a change and you'd have to go back to your product owner to further discuss what needs to be done. Otherwise, it's still better than receiving complains for wrong or missing discounts from your customer!

Top comments (0)