DEV Community

Cover image for ๐Ÿ› ๏ธ Writing Reliable Code from the Ground Up !
Sahil Dahekar
Sahil Dahekar

Posted on

๐Ÿ› ๏ธ Writing Reliable Code from the Ground Up !

The Coding Nightmare - Weโ€™ve all Experienced ๐Ÿ˜ต

Imagine spending hours crafting what you believe is the perfect piece of code, only to discover a cascade of bugs when you finally run it. The debugging marathon begins: fixing one issue reveals three more, and suddenly, your elegant solution looks like a house of cards ready to collapse ๐Ÿ› .

This was my reality during a local coding meetup while implementing Conway's Game of Life. I wrote extensive code, confident in my approach, only to find the implementation completely missed the mark. Hours of debugging later, I was frustrated and demoralized. ๐Ÿ˜ค

Java Code that i wrote during local coding meetup

Then I discovered Test Driven Development (TDD) โ€“ a methodology that flips traditional software development on its head ๐Ÿ”„

The Reactive Approach - Chasing Bugs and Deadlines ๐Ÿ•ฐ๏ธ

In traditional software development, the typical workflow looks like this:

  1. Write implementation code
  2. Develop tests after the fact
  3. Hope everything works as expected ๐Ÿคž

This approach is fraught with risks:

  • Developers often overlook edge cases ๐Ÿค”
  • Tests become an afterthought ๐Ÿ“
  • Bugs can slip into production ๐Ÿ›
  • Refactoring becomes increasingly difficult ๐Ÿงน
  • Confidence in code reliability diminishes ๐Ÿ˜Ÿ

Enter Test Driven Development - A new Hope for Devs ๐Ÿ™Œ :

TDD introduces a radical yet powerful approach: write tests first, then write the code to pass those tests.

Test Driven Development Illustration

The Red-Green-Refactor Cycle ๐Ÿ”

TDD follows a simple yet powerful cycle:

  1. Red: Write a failing test

    • Define the expected behavior
    • Ensure the test fails initially โŒ
  2. Green: Write minimal code to pass the test

    • Implement just enough code to make the test pass
    • Focus on solving the immediate requirement โœ…
  3. Refactor: Improve code without changing its behavior

    • Clean up the implementation ๐Ÿงน
    • Enhance readability and efficiency
    • Ensure all tests still pass

Red - Green - Refactor Cycle Diagram

Why TDD ? Unlocking the Key Benefits ๐Ÿ”‘

1. Superior Code Quality

  • Emphasis on clean, modular, and maintainable code
  • Testability becomes a primary design consideration
  • Clear documentation through tests ๐Ÿง 

2. Early Bug Detection

  • "Fail-fast" approach identifies issues immediately
  • Prevents complex, deeply embedded bugs
  • Reduces long-term debugging time ๐Ÿ›โžก๏ธ๐Ÿšซ

3. Confidence in Refactoring

  • Comprehensive test suite acts as a safety net
  • Enables continuous code improvement
  • Reduces fear of breaking existing functionality ๐Ÿ‘จโ€๐Ÿ’ป๐Ÿ‘ฉโ€๐Ÿ’ป

4. Clear Requirements and Design

  • Forces developers to think about requirements first
  • Improves understanding of problem domains
  • Facilitates better architectural decisions ๐Ÿ—๏ธ

5. Collaborative Documentation

  • Tests serve as living documentation
  • Provides clear specifications for team members
  • Bridges communication between technical and non-technical stakeholders ๐Ÿ‘ฅ

TDD in Action - A Step-By-Step Example :

Let's see TDD in action by building a Password Strength Checker

Step 1: Set Up Your Project

Make sure you have NodeJS installed in your system

Initialize a new Node.js project:

mkdir tdd
cd tdd
npm init -y
Enter fullscreen mode Exit fullscreen mode

Install Jest:

Jest is testing framework for JavaScript

npm install jest
Enter fullscreen mode Exit fullscreen mode

Update package.json to use Jest:

"scripts": {
    "test": "jest"
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Write the First Test ๐Ÿ”ด

Create a file named index.test.js in your project directory. Start with a simple test for weak passwords.

const checkPasswordStrength = require('./index');

describe('Password Strength Checker', () => {
    test('should return "Weak" for passwords less than 8 characters', () = { 
        expect(checkPasswordStrength('abc')).toBe('Weak');
    });
});
Enter fullscreen mode Exit fullscreen mode

Run the test using the following command:

npm test
Enter fullscreen mode Exit fullscreen mode

Youโ€™ll see an error because checkPasswordStrength doesnโ€™t exist.

Test failing error console output

Step 3: Implement the Initial Function ๐ŸŸข

Create a file named index.js and implement the initial checkPasswordStrength function to pass the first test.

function checkPasswordStrength(password) {

    if (password.length < 8) {
    return 'Weak';
    }

    return 'Medium'; // Default return value for now
}

module.exports = checkPasswordStrength;
Enter fullscreen mode Exit fullscreen mode

Run the test again:

npm test
Enter fullscreen mode Exit fullscreen mode

It should pass now.

First Test Passing console ouput

Step 4: Add More Tests and Logic Incrementally

Test for Medium Strength Passwords ๐Ÿ”ด

Update index.test.js to include a test for medium strength passwords.

describe('Password Strength Checker', () => {

    test('should return "Weak" for passwords less than 8 characters', () => {
        expect(checkPasswordStrength('abc')).toBe('Weak');
    });

    test('should return "Medium" for passwords with 8+ characters but no uppercase, numbers, or special characters', () => {
        expect(checkPasswordStrength('abcdefgh')).toBe('Medium');
    });
});

Enter fullscreen mode Exit fullscreen mode

Update index.js to handle medium strength passwords. ๐ŸŸข

function  checkPasswordStrength(password) {

    if (password.length  <  8) return  'Weak';

    const  hasUppercase  = /[A-Z]/.test(password);
    const  hasNumber  = /\d/.test(password);
    const  hasSpecialChar  = /[!@#$%^&*(),.?":{}|<>]/.test(password);

    if (!hasUppercase  &&  !hasNumber  &&  !hasSpecialChar) return  'Medium';

    return "Strong";
}

module.exports = checkPasswordStrength;
Enter fullscreen mode Exit fullscreen mode

Run the tests again to ensure they pass.

Test for Strong & Very Strong Strength Passwords ๐Ÿ”ด

Update index.test.js to include a test for strong & very strong strength passwords.

describe('Password Strength Checker', () => {
    test('should return "Weak" for passwords less than 8 characters', () => {
        expect(checkPasswordStrength('abc')).toBe('Weak');
    });

    test('should return "Medium" for passwords with 8+ characters but no uppercase, numbers, or special characters', () => {
        expect(checkPasswordStrength('abcdefgh')).toBe('Medium');
    });

    test('should return "Strong" for passwords with uppercase, lowercase, and numbers', () => {
        expect(checkPasswordStrength('Abcdef12')).toBe('Strong');
    });

    test('should return "Very Strong" for passwords with uppercase, lowercase, numbers, and special characters', () => {
        expect(checkPasswordStrength('Abcdef12@')).toBe('Very Strong');
    });
});
Enter fullscreen mode Exit fullscreen mode

Update index.js to handle strong strength passwords. ๐ŸŸข

function  checkPasswordStrength(password) {

    if (password.length  <  8) return  'Weak';

    const  hasUppercase  = /[A-Z]/.test(password);
    const  hasNumber  = /\d/.test(password);
    const  hasSpecialChar  = /[!@#$%^&*(),.?":{}|<>]/.test(password);

    if (!hasUppercase  &&  !hasNumber  &&  !hasSpecialChar) return  'Medium';

    if (hasUppercase  &&  hasNumber  &&  !hasSpecialChar) return  'Strong';

    return  'Very Strong';
}

module.exports = { checkPasswordStrength };
Enter fullscreen mode Exit fullscreen mode

Run the tests again to ensure they pass.

All tests passing console output

Step 5 : Optimization and Refactoring ๐Ÿ”ต

Once all tests are passing, it's time to improve and refactor the code.

Hereโ€™s the refactored version of index.js:

function  checkPasswordStrength(password) {

    if (password.length  <  8) return  'Weak';

    // Evaluate complexity conditions
    const  conditions  = [
    /[A-Z]/.test(password), // Uppercase letters
    /\d/.test(password), // Numbers
    /[!@#$%^&*(),.?":{}|<>]/.test(password), // Special characters
    ];

    const  score  =  conditions.filter(Boolean).length;

    // Determine strength based on score
    if (score  ===  0) return  'Medium';
    if (score  ===  2) return  'Strong'; // Uppercase + Number
    if (score  ===  3) return  'Very Strong'; // Uppercase + Number + Special char

    return  'Weak';
}

module.exports  =  checkPasswordStrength
Enter fullscreen mode Exit fullscreen mode

Instead of relying on multiple if-else conditions, we optimized the logic by calculating a strengthScore based on the password's complexity. This approach makes the code easier to read and maintain.

Edge Case :

What happens if the password is an empty string ('') or contains only whitespaces (' ')?

Try adding tests for these scenarios to strengthen the function. You can explore the complete solution here.

By letting test cases drive development, we ensured robust functionality while avoiding unnecessary complexity.

Myth-Busting TDD - Separating Fact from Fiction ๐Ÿค”

Myth #1:

TDD slows down development because developers have to write tests before writing code.

Reality:

TDD may seem slower at first, but it speeds up development in the long run by:

  • Catching bugs early ๐Ÿ”
  • Reducing debugging time ๐Ÿ•ฐ๏ธ
  • Improving code quality ๐Ÿš€

Myth #2:

TDD is just about writing tests and has no impact on the actual development process.

Reality:

TDD is more than just writing testsโ€”itโ€™s a design methodology that:

  • Clarifies requirements upfront ๐Ÿงฉ
  • Promotes modular and testable code ๐Ÿ“ฆ
  • Ensures incremental and iterative development ๐Ÿ”„

Myth #3:

TDD is only suitable for certain types of projects or specific programming languages.

Reality:

TDD is universal and can be applied to:

  • Projects of any size or complexity ๐ŸŒ
  • Any technology stack or programming language ๐Ÿ’ป

Its principlesโ€”writing tests first, focusing on small increments, and iterating based on feedbackโ€”are beneficial for all developers.

Beyond TDD - Whatโ€™s Next in your Development Journey ๐Ÿš€

Behaviour Driven Development (BDD) extends TDD's principles by focusing on behavior and business value. It uses more descriptive, narrative-style tests that non-technical stakeholders can understand. ๐Ÿ—ฃ๏ธ

Your TDD Toolkit - Recommended Resources ๐Ÿ“š

If you're curious to dive deeper into Test-Driven Development (TDD), here are some excellent resources:

  1. Test-Driven Development by TestDriven.io
    A comprehensive guide that walks you through the TDD process step-by-step, making it beginner-friendly and practical.

  2. Introduction to TDD by BrowserStack
    An insightful overview of TDD principles, benefits, and real-world applicationsโ€”great for developers of all levels.

  3. TDD Tutorial by freeCodeCamp
    A hands-on tutorial focusing on JavaScript and React, perfect for anyone working with modern frontend frameworks.

  4. Jest Documentation
    The official Jest docsโ€”a must-read for understanding how to write and run tests effectively with this powerful framework.

Wrapping Up ๐ŸŽ‰

Test Driven Development is more than a methodology โ€“ it's a mindset. It transforms coding from a reactive to a proactive process, where quality is built-in, not bolted-on. ๐Ÿง 

Ready to revolutionize your coding approach? Start small, be consistent, and watch your code quality soar. ๐Ÿš€

Happy Testing! ๐Ÿงช

๐Ÿ’ฌ Letโ€™s Connect!

Got any doubts or feedback? Share your thoughts in the comments below!

  • What topic should I cover in my next blog?
  • How did you like this one?

Your feedback and suggestions mean the world to me. Letโ€™s keep the conversation going! ๐ŸŒŸ

Top comments (2)

Collapse
 
prathamesh_kirad profile image
Prathamesh-V-Kirad

Interesting Topic and Helpful

Collapse
 
dilnaj_atar_ec48929ba3bfe profile image
Dilnaj Atar

Informative!!