TDD learning path for an intermediate level

For a developer with an intermediate level in Test-Driven Development (TDD), the goal is to deepen understanding of advanced principles, refine techniques, and learn how to apply TDD in more complex contexts.

Here we present a structured learning path to enhance your TDD skills and reach an advanced level.

1. Mastery of Advanced TDD Principles

It's always important to reinforce concepts to ensure practice is based on solid principles that help you improve your technique.

Key concepts to be clear on include effective refactoring, understanding when and how to refactor code without altering its functionality, as well as test-driven software design and how to apply software design patterns within the context of TDD. This is a highly debated topic in the industry, and you can surely contribute your own experience.

But where to start? A helpful book is Refactoring: Improving the Design of Existing Code by Martin Fowler. Although not exclusively focused on TDD, refactoring is a crucial part of the TDD cycle.

Also, Sandro Mancuso, founder of the London Software Craftsmanship Community (LSCC) discusses in several articles the relationship between TDD and software design. Mancuso raises questions any developer has encountered throughout their career and generates doubts that, through practice, will allow you to draw your own conclusions.

2. Katas 

As you progress, it's important to tackle problems that force you to think about architecture, system design, and how TDD influences design decisions from the start.

Medium-level Katas:

  • Kata Smart Fridge You must create a system for a 'smart fridge' capable of registering items being added or removed.
  • Kata Sudoku: The aim is to fill a 9 × 9 grid with numbers, using TDD.
  • Kata Mars Rovers: You will guide the behaviour of exploration robots that NASA has placed on Mars.

3. TDD in the Context of CI/CD

Integrating TDD into CI/CD processes not only improves software quality but also promotes more efficient and collaborative development practices.

TDD involves writing tests before the production code to define how the code should behave. This aligns well with CI/CD, where code is automatically integrated, tested, and deployed, ensuring new features or fixes don't break existing functionality

Setting up a CI pipeline that supports TDD

  1. Test automation: Ensure all unit and integration tests written following the TDD methodology are automatically executed as part of the CI pipeline.
  2. Test execution on each commit: Configure your CI system to automatically run the test suite every time a commit is made to the repository. This ensures any changes in the code don't break existing functionality.
  3. Fail fast: Configure the pipeline to stop if any tests fail, allowing for quick feedback to the development team.

Continuous Deployment with TDD

  1. Deployments based on successful tests: Ensure that new software versions are deployed based on the success of all TDD and integration tests. This means only deploying code that has passed all tests.

  2. Deployment Automation: Use CD tools to automate code deployment to different environments (testing, staging, production) once tests are successful. This speeds up the release process.

  3. Automatic rollbacks: Implement automatic rollback mechanisms in case deployment fails or causes production issues.

Some CI/CD tools that might be useful for automating processes include Jenkins, GitLab CI/CD, CircleCI, and GitHub Actions. These platforms offer a wide range of plugins and features to support TDD.

4. TDD for Legacy Code

Introducing tests into legacy code and refactoring such code are challenging but crucial tasks for improving software quality.

As you increase your TDD level, here we share some effective techniques and strategies to tackle these challenges:

Introducing tests into legacy code

Start by identifying parts of the code that are critical to the application's operation or that have a high error rate. These areas should be a priority for test introduction. Then, it's important to conduct an analysis of dependencies that complicate testing, using code analysis tools to understand these dependencies and evaluate how they can be isolated or mocked.

Before diving into unit tests, consider writing high-level tests, such as integration or acceptance tests. These can help ensure basic system functionality before refactoring internal code. Additionally, implement regression tests, document known errors, and write tests that capture these behaviours.

Use techniques like dependency injection, the use of interfaces, and mocking tools to isolate parts of the legacy code. This allows the introduction of unit tests, even in the presence of complex dependencies.

Refactoring legacy code with TDD

Address refactoring in manageable increments. Ensure each small change passes all existing tests before moving to the next. This approach minimizes the risk of introducing new errors.

For each change or improvement, follow the classic TDD cycle:

  • Write a test for the desired new behaviour or improvement.
  • Verify the test fails (this validates that the test is effective).
  • Implement the change and verify the test now passes.
  • Refactor the code to improve its structure, ensuring all tests continue to pass.

As you refactor, increase test coverage. This not only helps you detect errors but also ensures that the refactored code is easy to understand and maintain. For this, you can use tools such as JaCoCo (Java), Istanbul (JavaScript), or (Python).

Some recommended books for consolidating concepts include: Working Effectively with Legacy Code by Michael Feathers, offering specific strategies for working with legacy code, and Refactoring: Refactoring: Improving the Design of Existing Code by Martin Fowler, providing a detailed guide on refactoring techniques.

5. Community and Continuous Learning

Learning in a community will help you encounter new perspectives to enrich your knowledge. Participate in discussion groups and online communities about TDD. Platforms like Reddit and Stack Overflow can be invaluable.

Similarly, on our YouTube channel, we offer some workshops and practical sessions on TDD and software development. Interacting with other developers and experts will provide you with new perspectives and knowledge.

Final Recommendations

Review and Continuous Practice: Mastery of TDD comes with continuous practice and study. Regularly review your code and look for ways to improve your tests.

Training and Collaboration: Receiving training from experts is a very useful way to enhance your knowledge and stay up to date with industry trends. It also gives you the opportunity to receive immediate feedback on your practice.