
Master test driven development by applying classicist and outside-in TDD styles, boosted by ai tools, to build a real-world task manager with a RESTful API.
Explore test driven development through building a task manager with a RESTful API, hexagonal architecture, in-memory persistence, AI-assisted workflows, unit testing fundamentals, and a Spring Boot Kotlin stack.
Master the fundamentals of unit testing and test driven development by exploring arrange, act, assert, the single assertion rule, mocking, and testing behavior versus state.
Explore the two accompanying GitHub repositories, clone the first for unit testing fundamentals, review the source package, and read the Readme for tech stack and additional material.
Explore how to test software by setting up the test world, triggering actions, and observing side effects across unit, integration, and end-to-end tests, illustrated by a bank deposit.
Explore how the test pyramid stacks unit, functional, end-to-end, and manual tests, and why higher levels cost more to execute and write.
Define unit tests as fast, reliable, and isolated to enable quick feedback loops and rapid iteration in TDD, while avoiding external dependencies.
Unit tests boost confidence by validating code works and enable safe refactoring. They reveal left-shifting bugs early, reduce maintenance costs, and document production behavior as living executable specifications.
Explore how test naming impacts readability and documentation, using should suffixes, given-when-then patterns, and specification style to describe production behavior.
Refactor messy production tests to improve readability and clearly express behavior. Write the tests you'd want to read before checking them in to version control.
Apply the triple a rule: arrange, act, assert, to structure unit tests. Show in a booking validator that given invalid bookings (no id) throw, while given valid bookings do not.
Apply the single assertion rule to unit tests, showing one reason to fail, with divide by zero and a password generator of length 12 and at least one digit.
watch the test fail to verify it fails for the right reason and that the diagnostics are clear, a core lesson in test driven development.
learn the isolation principle in test suites by understanding setup, test, and cleanup hooks, ensuring each test runs independently and leaves the system unchanged to prevent downstream failures.
Explore mocking and stubs to test a booking service in isolation, injecting a mock booking gateway, defining stubbed responses, and verifying calls without touching the third party system.
Learn how mocks simulate real objects and how dummy objects satisfy tests without expensive instantiation, illustrated by a used-ids example in outside-in test driven development.
Learn to distinguish testing behavior from testing state by verifying observable retries in a gateway, avoiding private state checks, and ensuring tests stay robust through refactoring.
Recap essential testing concepts, including the test pyramid, unit tests, readable naming, and the arrange-act-assert with given-when-then, plus isolation and mocking in TDD.
Write code using test driven development to build the task manager's application logic, exploring triangulation and fake it till you make it through a bold refactor.
Explore the second GitHub repo, clone it to experiment, and review the readme’s branches for classicist, outside-in, and TDD with starting points and solutions, plus tech stack and tools.
Explore the TDD cycle—write a failing unit test to enter red, make it pass to green, then refactor in yellow, and repeat the loop until no new failures.
Explore validation of task status transitions in a hands-on task manager app, using test driven development and the classicist approach to enforce title and assignment rules for moving to done.
Apply test-driven development by faking the validate function to pass tests, design the task validator api, and progress from red to green toward a real solution.
Triangulation in test-driven development teaches attacking a problem from a different angle, writing a failing test, refactoring, and moving from red to green to yellow states.
Explore TDD in action with AI through the red-green-refactor cycle, using failing tests, the obvious implementation, and triangulation to validate task transitions.
Apply TDD to enforce that a task can only transition to done when assigned, using red state, green state, failing tests, and careful refactoring.
Demonstrate a tdd cycle from red to green to refactor, add a failing test for blocked tasks that cannot move to done, and align code with the open closed principle.
Refactoring demonstrates extracting task validation rules into a transition rule interface with implementations like title must not be blank and title must be at least three characters long.
Implement a new 'Task must not be done' rule and write a failing unit test to prevent transitioning to done when the task is already done, following the open-closed principle.
Wraps up the classicist test driven development cycle by writing a failing test with no new requirement. Highlight test contravariance and a single test file for five production classes.
Apply a real world test driven development approach to build a task manager with a restful API, in-memory persistence, mocks, and functional tests guiding design.
Demonstrates the outside-in TDD cycle, starting with a failing functional test to drive a production layer, using test outcomes as a compass through inner cycles and defining task manager requirements.
Register users, create tasks, update task status, and retrieve all tasks through a public RESTful API built with Spring Boot, Kotlin, and Spock.
Learn to write a failing functional test for user registration in a spring boot app, posting to api/users with a json payload and expecting a 201 created response.
guide to test-driven development of a user api handling post /api/users, with a failing test for existing username, and implementing 400 response via a user service and registration dto.
Set up domain and api structure, define a user service interface with a create function, and implement a user data class (id, username, password) for the test compile.
Practice test-driven development by invoking the user service's create method with a username and password, handling exceptions with try-catch, returning proper status codes, logging failures, and validating that tests pass.
Drive test driven development by writing a failing unit test for the user api to register a new user and assert a 201 created response with a location header.
Learn how functional tests act as a north star guiding incremental implementation in a tdd workflow, running the app and fixing the user service to pass the registration test.
A unit test for the user service creates a mock user repository, invokes create with an existing username, and expects a username exists exception after checking Findall.
Explore the happy path of creating a new user with unique username validation and a mocked repository. Implement the save method and user service to pass unit tests.
Implement the user repository using a hexagonal architecture approach, run functional tests, and complete the user registration feature with an in-memory repository that supports find all users.
practice outside-in tdd by writing a failing functional test for creating a new task, including json payload, http post to /api/tasks, and validating 201 response and task id.
Apply tdd to design a task api by reproducing a failing unit test that returns 400 when creating a task for a non-existent user, with dto and mocks.
Implement the task API's create method to accept a create task request and return a task. Define the task DTO with title, status, optional assignee id, and blocked flag.
Implement and test the task creation API via a create call with title, status, assignee id, and blocked. Return created task at /api/tasks/{task.id} and handle failures with response codes.
Follow the test driven development cycle to identify the next implementation point by running a failing functional test, then implement the task service and handle an unsupported operation exception.
Ensure the task service throws a user not exists exception when creating a task with a non-existent assignee, via the find by id on the user repository.
Execute the happy-path for creating a new task by mocking the user repository, running the task service, and asserting a single save call with a valid task.
Enhance the user repository by implementing find by id to return the expected user or null for non-existent ids, guided by tests and debugging failures in the TDD workflow.
Apply tdd with ai to complete the task creation feature by implementing an in-memory task repository, adding a test case and suite, and passing the functional test.
Write a failing functional test for updating a task status via patch, validate in progress and done transitions, with 200 responses and consideration of 404 errors as described.
Advance the task API with TDD by writing a failing unit test for updating a non-existent task, triggering a 404 and a task not found exception.
Update the task API by validating the update status with a task id and status, returning the updated task, and ensuring tests handle unsupported operation and casting errors.
In this lecture, we implement test-driven development for a task API, making a 404 on task not found and a 400 on invalid status transitions, using arrange-act-assert and service stubs.
Execute the functional test, observe a failure, and implement update status in the task service by adding a validation package with a task transition validator.
Add a second test for the task service to verify a non-existent task triggers a task not found exception, while validating transitions via the repository and validator.
Implement the happy path for updating a task status by writing a failing unit test, mutating status, saving the updated task in the repository, and completing the repository implementation.
Implement the find by ID method by saving the task and fetching it by ID, then validate that the actual task equals the expected task, including test fixes.
Review applying the TDD cycle to a task project, implementing register, create task, and update status until the test goes green. Plan future enhancements with mocks and refactoring.
Assess the pros and cons of two styles and understand the trade-offs to know when to use each approach. The instructor shares their personal opinion on the two styles.
Classicist TDD strengths: tests loosely coupled to production code due to minimal mocking, enabling bold refactoring, delayed design decisions, and an evolutionary mindset via you ain't gonna need it principle.
Highlight classicist TDD weaknesses: reduced test readability, large tests with heavy setup and assertions that hinder documentation; note complex refactorings and treating the API as secondary, risking overengineering.
Outside-in TDD designs code from tests, defines collaborators and behavior upfront to minimize refactoring, and produces readable tests that document the system while driving client-focused architecture.
Explore the weaknesses of outside-in TDD, including tight coupling between production and test code, heavy reliance on mocks, and refactoring brittleness that hampers algorithmic development.
TDD is a tool that enables software design, not an automatic guarantee. Balance outside-in TDD with classic TDD, applying each where appropriate and guided by SOLID principles.
Discover how to safely integrate AI tools into test-driven development, using Kirchherr and GPT-4 to accelerate building the final task manager feature while maintaining control.
Leverage test driven development with ai tools to craft a failing functional test for fetching all a user's tasks via api/tasks, then validate the 200 response once implemented.
Advance TDD by writing failing unit tests for the task API's get tasks by assignee, validating 200 with tasks, 200 with an empty list, and 400 for a null assignee.
Demonstrate test-driven development with AI to implement the task service, writing failing tests, implementing get tasks by assignee, and handling repository methods with unsupported operations.
Apply test-driven development to turn red tests green by implementing the task service to delegate to repository find by assignee id and update specs to return tasks or empty lists.
Implement the task repository to filter tasks by assignee id, enabling users to retrieve their tasks while validating changes with passing tests.
Extend the task manager app to retrieve tasks using AI tools like cursor and GPT four, while practicing disciplined test driven development and maintaining process control.
Explore how test driven development with AI drives real world workflows, compare classicist and outside-in styles, and ship great software through ongoing testing and refactoring.
Test-Driven Development in Action with AI - Nikos Voulgaris.
Apply Test-Driven Development in real-world software, integrate AI tools into your workflow, and elevate your professional skills.
Does Test-Driven Development sound fun and promising, but feel limited to toy examples like Tic-Tac-Toe or FizzBuzz?
I've been there. It was frustrating to see such a powerful technique presented in ways that felt disconnected from the complexities of real-world software. That's why I created this course - to help software engineers confidently apply TDD in production codebases, and discover its full potential beyond the basics.
In this course, I share the hard-earned lessons from my own journey mastering TDD - distilled into a practical, structured, and accessible format. You'll learn how to build maintainable systems from the ground up, and how to integrate AI tools into your development workflow to move faster without sacrificing control or the benefits of TDD.
By the end of the course, my goal is for you to walk away with real-world, hands-on skills, a deeper understanding of TDD - and hopefully, to have enjoyed the ride.
What You'll Build
Throughout the course, we’ll build a task manager application that exposes a RESTful API. Users will be able to register, create tasks, update task statuses, and retrieve their tasks.
We’ll follow the principles of hexagonal architecture - not dogmatically, but by letting it emerge naturally through our TDD process. To keep things simple, we’ll use in-memory persistence.
Technologies used: Spring Boot, Kotlin, and Spock. No prior in-depth knowledge of these tools is required. The focus isn’t on specific technologies - it’s on mastering the methodology, mindset, and discipline of Test-Driven Development.
You’ll also learn how to thoughtfully integrate AI tools to support (not replace) your decision-making and enhance your TDD workflow.
Why Learn From Me?
I'm a software engineer with over 12 years of professional experience. I started exploring TDD around a decade ago, and have been using it extensively in production for the past 8 years. Along the way, I've helped dozens of engineers adopt TDD through in-person coaching, pair programming, and online workshops.
I know the pitfalls, the resistance, the confusion - and the "aha" moments that make it all click. I created this course to pass those insights on to you.