15 min read

8 Practical Test Driven Development Examples for Real World Code

8 Practical Test Driven Development Examples for Real World Code

"Just write the test first." It sounds so simple, right? But I remember staring at a blank test file for a new feature, completely paralyzed. My mind raced: "How can I test something that doesn't exist? What if I write the wrong test? Am I just wasting time?" The promise of bug free, beautiful code felt miles away from the reality of my blinking cursor.

This article is the guide I wish I had back then. It's a journey from that initial doubt to genuine "aha" moments. We're not going to just talk about the theory of Test Driven Development. We'll walk through eight concrete test driven development examples, starting with the simple stuff and leveling up to the gnarly, real world challenges you actually face, like user authentication, shopping carts, and even complex API endpoints.

This isn't about dogma. It's about a shift in mindset that turns testing from a chore into your secret weapon for designing and building better software. TDD is a core part of building high quality systems, a pillar in the world of Agile Development Best Practices. Let's start the journey and see how writing a failing test can be the most productive thing you do all day.

1. Calculator Application with Basic Arithmetic Operations

Our first stop is the humble calculator. I know, I know, it sounds like a "hello world" example, but stick with me. This is our dojo, our training ground. It's a beautifully contained problem that lets us practice the core TDD rhythm—Red, Green, Refactor—without getting lost in complex logic. Before we even dream of writing an add or subtract function, we first have to describe, in a test, what we want that function to do. This one small change flips the entire development process on its head.

A hand drawn calculator sketch with various design annotations and handwritten labels, illustrating a concept.

The real power here isn't proving that 2 + 2 = 4. It's when we start thinking like a real user. What happens when someone tries to divide by zero? How should our calculator handle floating point numbers? Instead of waiting for these edge cases to become late night production bugs, TDD invites us to write a failing test for them first. This test becomes a contract, a promise that our future code must fulfill. It's a safety net we weave before we even start climbing.

Strategic Breakdown

  • Why it's a Classic: It's simple enough to grasp in minutes but has enough tricky spots (like division by zero) to make the value of TDD obvious.
  • The TDD Flow in Action:
    1. Red: Write a test for add(2, 3) and expect it to return 5. Of course, it fails. The add function doesn't even exist. This is a good thing! We've defined a clear goal.
    2. Green: Write the absolute simplest code to make the test pass. Seriously. You could even just write def add(a, b): return 5. Now, add a second test for add(4, 6). Watch it fail. Now you're forced to write the real logic: return a + b.
    3. Refactor: The code is tiny, so not much to refactor yet. But as we add more operations, we might spot ways to clean up our code, all while our tests ensure we don't break anything.
Actionable Takeaway: Use the calculator as a "kata," a practice routine for your team. It's a low stakes way to build the muscle memory for thinking test first. It's one of the most effective test driven development examples for getting everyone comfortable with the rhythm.

2. String Utility Library Development

Okay, we've warmed up. Now let's build something genuinely useful: a string utility library. Almost every application I've ever worked on needed to sanitize, format, or slice up text. By starting with tests, we define exactly how we want our strings to behave. This forces us to face the messy reality of string manipulation head on, instead of discovering it when a user pastes in an emoji or a null value and crashes the app.

This is where TDD starts to feel like a superpower. What should a truncate function do with an empty string? How does your sanitize function handle different character encodings? Instead of guessing and writing defensive code, you write an explicit test for each scenario. Each test is a specific question, and the code you write is the answer. This creates a rock solid, well documented contract for each function. These practical test driven development examples build a reliable foundation the rest of your app can stand on.

Strategic Breakdown

  • Why it's a Classic: It's a common programming task that is full of hidden traps like empty strings, null values, and special characters.
  • The TDD Flow in Action:
    1. Red: Write a test for a reverse("hello") function, expecting "olleh". It fails because reverse is just a figment of our imagination.
    2. Green: Implement the simplest possible code to pass, maybe return input.split('').reverse().join(''). Now, level up. Add a test for reverse("") expecting "". Then another for a null input. Make them all pass.
    3. Refactor: The first version is okay. But as you add more functions like capitalize or truncate, you might notice you're checking for null inputs everywhere. This is a perfect signal to refactor that shared logic into a helper, keeping your code DRY (Don't Repeat Yourself).
Actionable Takeaway: When building your string utilities, write the "unhappy path" tests first. Create a contract for how your functions will handle empty strings, null values, and whitespace. This builds resilience in from the very start.

3. User Authentication and Authorization System

Alright, let's raise the stakes. Building a user authentication system is where TDD goes from a nice to have practice to an absolutely critical one. Security isn't a feature you can just add on later. It has to be baked in from the beginning. TDD forces this security first mindset by making you define what "secure" means in code before you write a single line of login logic.

Diagram illustrating a JWT authentication flow with a user, an 'ingind' component, and a login shield.

I once spent hours debugging a permission issue because the rules were so complex and implicit. With TDD, we avoid that. We write explicit tests for every scenario: invalid passwords, expired tokens, insufficient permissions. We test the happy path, of course, but more importantly, we codify the failures. "What happens when a regular user tries to access an admin endpoint?" We answer that question with a failing test, which becomes our best defense against security bugs. This is one of the most powerful test driven development examples because it directly protects your users and your application.

Strategic Breakdown

  • Why it's a Classic: It operates in a high stakes domain where mistakes have serious consequences. It forces you to define security policies clearly from the start.
  • The TDD Flow in Action:
    1. Red: Write a test for your login endpoint with bad credentials. Assert that the response is a 401 Unauthorized status. The test fails miserably, as it should.
    2. Green: Implement the bare minimum to make the test pass. Maybe it's just a hardcoded check. Now, write a test for a successful login and make that pass too. You're building up the logic layer by layer.
    3. Refactor: With the basic flow working, it's time to make it secure. Refactor the implementation to use proper password hashing with a library like bcrypt. Your tests shouldn't change at all, but the code underneath becomes production grade. The tests give you the confidence to make this critical change without breaking anything.
Actionable Takeaway: Think like an attacker. Create a "threat model" in your test suite. Write tests that check for common vulnerabilities like SQL injection in the username field. By describing the attack in a test, you ensure your code is built to defend against it.

4. E Commerce Shopping Cart Implementation

Let's talk about money. An e commerce shopping cart is where business logic and code collide. A single bug in the total calculation or discount application can directly impact revenue. This is a perfect scenario where TDD isn't just a development practice; it's a business necessity. Before a user can add a single item, we must define, in code, what a "correct" cart looks like.

Hand-drawn shopping cart connected to a conceptual user interface displaying total amount and selection options.

This example shines because it's all about managing complex state and rules. What happens when a discount code is applied? How is sales tax calculated for different regions? Can a user add an out of stock item to their cart? TDD prompts us to answer these questions by writing a failing test for each scenario first. This process creates an ironclad safety net, ensuring every piece of business logic, from updating quantities to validating coupons, works exactly as intended before it ever touches a real customer's wallet.

Strategic Breakdown

  • Why it's a Classic: It moves beyond simple algorithms into stateful application logic, which is where many tricky bugs hide. It shows how tests can serve as living documentation for complex business rules.
  • The TDD Flow in Action:
    1. Red: Write a test called test_add_item_to_empty_cart. Assert that after adding an item, the cart's total price matches the item's price and the item count is 1. The test fails because our Cart is just a dream.
    2. Green: Create the most basic Cart class and add_item method to make the test pass. It might just be a simple list of items and a loop to calculate the total.
    3. Refactor: Now add a new test, test_add_same_item_twice_updates_quantity. This will likely fail. You'll need to refactor your add_item logic to be smarter. This iterative process of adding a test and refining the code is the heart of TDD.
Actionable Takeaway: Use a "test data builder" pattern. Instead of manually creating complex carts with multiple items and discounts in every single test, a builder can generate these scenarios for you. This keeps your tests clean, readable, and focused on the one thing you're trying to prove.

5. RESTful API Endpoint Development

An API is a contract. It's a promise your service makes to its consumers. With Test Driven Development, you write the terms of that contract first. Instead of manually testing your endpoints with a tool like Postman after you've built them, you codify the expected requests, responses, status codes, and error messages into an automated test suite. Your tests become living, executable documentation for your API.

The real magic happens when you start testing for the messy, real world interactions. How should your API respond to a malformed request body? What status code does it return when a user asks for a resource they don't have permission to see? By defining these scenarios in tests first, you build a predictable and resilient API. This is one of the most practical test driven development examples for any backend engineer, because it leads directly to more reliable and maintainable services.

Strategic Breakdown

  • Why it's a Classic: It forces you to think like a consumer of your own API from day one, considering everything from headers and validation to proper HTTP status codes.
  • The TDD Flow in Action:
    1. Red: Write a test for a GET /api/widgets/1 endpoint. Use a testing client to make the request and assert that the response status is 200 and the body contains the widget data you expect. It will fail because the route doesn't exist.
    2. Green: Create the minimal route handler and logic to fetch the widget and return it as JSON, just enough to make the test pass. Now, write a new failing test for GET /api/widgets/999 (a widget that doesn't exist) and expect a 404 Not Found. Implement the logic to make that pass.
    3. Refactor: As you add POST, PUT, and DELETE endpoints, you'll start to see repeated logic. Maybe you're fetching objects or checking permissions in the same way. This is your cue to refactor that logic into shared helpers or middleware, all while your tests ensure you don't break the contract.
Actionable Takeaway: Let your test files mirror your API structure (e.g., tests/api/test_widgets.py). This makes it easy for a new developer to understand an endpoint's complete behavior—successes, failures, and validation rules—just by reading the tests. For deeper insights, you can learn more about REST API design principles to strengthen your test first approach.

6. Data Validation and Business Rule Engine

The business logic is the heart of your application, and a validation engine is its guardian. It ensures that only clean, correct data gets into your system. Using TDD to build this engine isn't just a good idea; it's a strategic move. By defining your business rules as a series of tests first, you create an executable specification of what your system considers valid, from a simple email format to a complex rule like "a user's discount code is only valid if their total purchase is over $50."

This approach turns abstract requirements into concrete, verifiable code. Instead of hoping your if statements cover all the edge cases, you write tests that explicitly define them. What if a user's age is exactly the minimum required? What if a dependent field is missing? TDD forces you to confront these scenarios. This makes your validation logic not just robust, but also self documenting. This is why TDD for validation is one of the most powerful test driven development examples for building truly resilient applications.

Strategic Breakdown

  • Why it's a Classic: It directly translates business requirements into testable code, making it a perfect example of tests as living documentation.
  • The TDD Flow in Action:
    1. Red: Write a test for a UserRegistrationValidator that checks if the age field is below 18. Assert that it produces a specific validation error. The test fails because the validator doesn't exist.
    2. Green: Implement the simplest possible validator to make the test pass, maybe a basic if age < 18 check.
    3. Refactor: As you add more rules (password complexity, username uniqueness), you'll see an opportunity to refactor. You could create a more generic rule engine where individual rules are small, composable objects. This makes your validation logic cleaner and easier to maintain. For instance, you could find helpful tips on validating raw JSON post request bodies for a Django backend by reading more on this topic at kdpisda.in.
Actionable Takeaway: Before writing code, write down your business rules in plain English. Then, turn each rule into a failing test. This ensures you have 100% test coverage for your business logic and creates a powerful safety net that protects your core rules from accidental changes.

7. Database Query and ORM Functionality Testing

We often treat the database as a "black box" that's hard to test. But bringing your database queries and ORM interactions into the TDD fold is a game changer. Instead of writing a query and then manually checking the database to see if it worked, you first write a test that defines what a successful database interaction looks like.

This approach forces you to be explicit about your data. You write a test asserting that a function get_active_users() returns exactly three users, all with the status "active," and that they're ordered by their signup date. This test fails, guiding you to implement the precise ORM code needed to make it pass. This is one of the most important test driven development examples because it prevents the subtle, hard to find bugs that often come from incorrect data retrieval. I've lost countless hours to bugs that were simply the result of a wrong database query; TDD helps prevent that pain.

Strategic Breakdown

  • Why it's a Classic: It tackles a common source of bugs: incorrect data retrieval. It makes database interaction a core, verifiable part of your application's logic.
  • The TDD Flow in Action:
    1. Red: In your test, set up a specific database state: create five users, but only make three of them "premium". Then, call a non existent function fetch_premium_users() and assert that it returns only those three specific users. The test fails.
    2. Green: Implement the fetch_premium_users() function with the simplest possible ORM query to select users where is_premium is true. Run the test again and watch it pass.
    3. Refactor: Now look at the query you wrote. Is it efficient? Could it cause an N+1 problem down the line? Now is the time to optimize it, perhaps by adding a select_related, knowing your tests will stay green and confirm you haven't changed the outcome.
Actionable Takeaway: Use an in memory database like SQLite for your tests. This makes them incredibly fast and ensures each test runs in isolation. Combine this with "factories" to generate consistent test data, and you'll have a clean, fast, and reliable safety net for your entire data layer.

8. State Machine and Workflow Engine Implementation

Complex processes like an order fulfillment pipeline can quickly turn into a tangled mess of if/else statements that nobody understands. A state machine brings order to this chaos. Applying TDD here is like drawing a map before you enter a dense forest. You define every possible state, every valid transition, and every side effect with absolute clarity before you get lost in the implementation details.

The real magic is in testing the negatives. It's not just about proving an order can go from Processing to Shipped. It's about writing a test that proves an order cannot jump from Pending directly to Delivered. By encoding these rules in your test suite first, you build a robust system that prevents impossible things from happening. This is one of the most advanced test driven development examples, perfect for building bulletproof business applications.

Strategic Breakdown

  • Why it's a Classic: It directly tames complexity. TDD provides the perfect framework to define the rules of a complex system before you write a single line of state management code.
  • The TDD Flow in Action:
    1. Red: Write a test asserting that a newly created Order is in the Pending state. It fails. Then write a test that says calling process_order() on it should change its state to Processing. That fails too.
    2. Green: Implement the minimal code to make those tests pass. Now, add a test to ensure that calling ship_order() on a Pending order throws an error. Watch it fail, then implement the logic to prevent this illegal move.
    3. Refactor: As you add more states (Shipped, Cancelled), you might decide to use a dedicated state machine library instead of simple properties. Your existing tests become your safety net, ensuring this major refactor doesn't break any of your carefully defined rules.
Actionable Takeaway: Use a state diagram as your testing blueprint. For every arrow on your diagram, write a test that proves the transition works. For every two states that don't have an arrow between them, write a test that proves the transition is forbidden. This turns your visual design into a comprehensive, executable test suite.

Test Driven Development: 8 Example Comparison

Example Implementation Complexity Resource Requirements Expected Outcomes Ideal Use Cases Key Advantages
Calculator Application with Basic Arithmetic Operations Very low — simple functions and tests Minimal — unit test framework only Solid TDD fundamentals, quick red green refactor cycles TDD onboarding, demos, beginner exercises Fast feedback, clear pass/fail criteria
String Utility Library Development Low–moderate — multiple transformations and edge cases Small — test data sets, encoding/regex considerations Reliable string utilities with edge case coverage Utility libraries, input sanitization, formatting tools Reusable functions, comprehensive edge case tests
User Authentication and Authorization System High — security, tokens, RBAC and timing concerns High — mocks for external services, security libraries, test doubles Secure auth flows, fewer security regressions Applications requiring login, RBAC, MFA Catches security issues early; enables safe refactoring
E Commerce Shopping Cart Implementation High — stateful logic, pricing and concurrency Moderate–high — monetary libs, inventory integration, fixtures Correct pricing, discount/tax rules, concurrency safe carts Retail platforms, checkout systems, order management Prevents pricing bugs; validates business rules and persistence
RESTful API Endpoint Development Moderate — request/response and status handling Moderate — HTTP mocking, serialization, auth stubs Stable API contract, correct status codes and error formats Client server integrations, microservices, public APIs Tests define API contract; enables parallel client/server work
Data Validation and Business Rule Engine Moderate — many conditional and cross field rules Moderate — rule libraries, localization resources Consistent validation, clear error messages, rule reuse Forms, enterprise business rules, input validation layers Makes business rules explicit and testable; reduces input bugs
Database Query and ORM Functionality Testing Moderate–high — queries, transactions, relationships High — test databases, fixtures, transaction tooling Correct CRUD behavior, relationship integrity, migrations safety Persistence layers, data heavy applications, schema changes Catches DB issues early; validates data integrity and queries
State Machine and Workflow Engine Implementation High — transitions, guards, side effects, async flows High — workflow frameworks, event systems, complex mocks Predictable workflows, enforced valid transitions and side effects Order processing, approvals, onboarding, pipelines Explicit state transitions; prevents illegal states and unexpected side effects

Your Turn to Build with Confidence

We've journeyed from a simple calculator to a complex workflow engine. Across all these scenarios, a clear pattern emerges. Test Driven Development isn't really about writing tests. It's a design practice. It forces clarity, predictability, and simplicity into the chaotic process of building software.

The real magic is letting the tests guide you. Each failing test is a question: "What should the code do next?" Each passing test is a confirmation: "Okay, we've achieved that goal." The refactor step is where we polish the story, making it not just correct, but elegant and easy for the next person to understand.

Distilling the Core Lessons

Let's pause and reflect on what we've learned from these test driven development examples.

  • Tests as Design Documentation: The tests for our API endpoint did more than check for a 200 status code. They documented the exact JSON structure and error messages. A new developer could read those tests and understand the API's contract without ever seeing the implementation.
  • Isolating Complexity: With the database and state machine examples, we saw the power of testing in isolation. By focusing on one piece of the puzzle at a time, we could build complex systems out of simple, provably correct components.
  • Confidence in Refactoring: The authentication and shopping cart systems are guaranteed to change. Business rules evolve. With a comprehensive test suite, we can refactor those critical pieces with confidence, knowing our tests are a safety harness that will catch us if we make a mistake.
The goal of TDD is not to have a suite of tests. The goal is to have a well designed, maintainable system. The test suite is a wonderful, confidence boosting side effect.

Your Actionable Path Forward

Theory is easy, but the real learning happens when you write the code. You don't need to rewrite your entire application overnight. Instead, think like a TDD practitioner: start with the smallest possible step.

  1. Pick One Small Feature: Look at your next task. Find a single, small, well defined piece of work. A new API endpoint, a small utility function, a single component.
  2. Commit to the Cycle: For just that one feature, commit to the "Red, Green, Refactor" cycle. Write a failing test first. Write the minimum code to make it pass. Then, clean it up.
  3. Embrace the "Slowness": It will feel slower at first. That's okay. You're trading frantic typing for focused thinking. This initial investment pays for itself tenfold in reduced debugging time later.
  4. Pair with a Colleague: Grab a teammate and try it together. Talking through the process is one of the fastest ways to learn and build a shared understanding of quality.

The test driven development examples in this article are your map. They show you the terrain. Now, it's your turn to take the first step. The confidence that comes from building on a foundation of tests is a superpower. It allows you to move faster, build better products, and sleep better at night.


Struggling with technical debt or trying to build a strong engineering culture? As a consultant specializing in scalable architecture, I help teams implement practices like TDD to build better products, faster. Let's connect at Kuldeep Pisda and talk about building your next feature with unshakable confidence.

Subscribe to our newsletter.

Become a subscriber receive the latest updates in your inbox.