Thinking in Test-Driven Development

Abdulkerim Yıldırım
Mobillium
Published in
6 min readDec 19, 2023

Test-Driven Development (TDD) is a software development approach where tests are written before the actual code. Within this piece, we discuss the mistakes to avoid in TDD, in addition to key considerations essential for its successful implementation in your organization.

The TDD process involves three main steps:

1. Writing a Failing Test: Start by writing a test that outlines a new function or improvements to an existing one. This test is expected to fail initially, as the corresponding code doesn’t exist yet.

2. Writing the Minimum Code: Implement the minimum code to pass the test. This step focuses on meeting the test’s requirements with minimal code implementation.

3. Refactoring the Code: After passing the test, refactor the code to improve its structure, readability, and performance. It’s important to ensure that these improvements don’t cause the tests to fail.

Think Before You Code: Pre-Coding Strategies

· Start with the End in Mind: Before jumping into coding, define the expected behavior from the feature or function. Understanding this helps to write meaningful and effective tests.

· Design with Testability in Mind: Plan your code structure with testing in mind. A well-organized, modular code makes writing and maintaining tests more simple.

· Consider Edge Cases and Boundaries: Remember to include edge cases and boundary conditions when developing your tests. This ensures your code can handle diverse scenarios, improving its strength and reliability.

Benefits of Thinking in TDD

· Early Bug Detection: Functional issues are identified early in the development process by writing tests before coding. This minimizes the chances of bugs reaching production.

· Improved Code Quality: TDD encourages writing modular and maintainable code. This leads to stronger applications and simplifies the process for developers to understand and build upon the codebase.

· Confidence in Refactoring: With a comprehensive set of tests in place, developers can refactor code confidently, knowing they’re less likely to introduce new bugs. This supports an environment of ongoing improvement.

· Documentation via Testing: Tests serve as live documentation, providing insights into the intended functionality of the code. This is particularly important in large-scale projects with multiple contributors.

Challenges and Strategies in TDD

One of the biggest challenges in TDD is creating extensive test scenarios, which can lead to complex and costly code maintenance. Senior developers might resist transitioning to TDD, fearing its impact, while juniors might welcome it for better code understanding. A common concern is that too much test code compared to actual code can lead to worries about efficiency and cost.

Despite the potential of test scenarios to ensure zero-error products, they can triple the coding effort. This raises questions about the trade-offs between thorough testing and productivity, especially when urgent features demand quick delivery. Here’s how to balance test thoroughness with practical project requirements to avoid excessive costs:

Maintaining Code

New features or changes often break tests, as they’re not comprehensive and behavior-specific. This requires rewriting and adapting tests with new mock data.“Refactoring was changing the implementation details without breaking tests”, says Martin Fowler, author of “Refactoring”. Approaches like “Programmer Anarchy”, “Spike and Stabilize”, and “Lean Software Development” question the necessity of test-first approaches. In Silicon Valley, many start-ups skip writing tests to speed up product development, arguing that customer feedback is faster for product validation. They believe TDD is better for scaling, not for initial development.

Being a Part of the TDD Software Team

Duct Tape Programmers, known for quick fixes in coding like using duct tape in real life, often seem successful at first glance. They focus on fast, practical solutions rather than best practices or long-term planning, leading to effective short-term fixes but potentially unmaintainable code over time.

Businesses and product teams usually value these programmers for their rapid results. But developers might resist writing tests, believing they don’t offer immediate value to focus on building software.

Effective Strategies for TDD

In TDD, it’s important not to treat every new feature as a new class requiring separate test code. The goal should be minimal testing that fulfills the specific story or use case, focusing on behavior rather than classes.

For instance, in Agile, user stories are concise: “As a [type of user], I want [an action] for [benefit/value].” Like a website visitor wanting seamless social media login access.

Consider a banking app: what matters is the user’s ability to transfer money, not the underlying code details. Tests should address customer-facing functionalities and behaviors, not just technical implementation.

Tests for complex scenarios are useful during development but should be removed post-implementation. Tests must run independently and focus on the overall story or scenario, not individual methods. This ensures maintainability and clarity, preserving the intended behavior.

Red-Green-Refactor Process

Red: Start by writing a basic test that might not work or even compile.
Green: Quickly make the test pass, committing whatever is necessary.
Refactor: Clean up the code, removing any unnecessary added parts just to pass the test.

Steps:

  1. Write a test
  2. Make it compile
  3. Run it to see that it fails
  4. Make it run
  5. Remove duplication

Don’t worry about writing perfect code at first. The goal is to get the test to pass (green). Each step has its own purpose. Focus on refining and improving the code during the refactoring stage. First, make the code work; then make it good.

Refactoring

The refactoring step is all about cleaning up your code. It’s where you remove any repetitive parts. Remember, don’t write new tests during this step. Just make safe changes to improve the design. Only add tests for a new class if what it does has changed.

Kent Beck says, “Dependency is the key problem in software development at all scales”. Tests shouldn’t rely on tiny details but on the code’s main functions and agreements. This way, you can change the inside parts of your code without messing up your tests. Focus on testing the main functions, not the small inner workings, and don’t expose all your code just to test it.

Notes from: TDD Where did it all go wrong

The main purpose of testing in software development is to check new features, not just individual parts of the code. The approach is to first write code that’s good enough to pass the test (even if it’s not perfect) and then improve it. You don’t need to create new tests for small changes you make later.

In conclusion; thinking in Test-Driven Development (TDD) significantly changes how we develop software, leading to better, stronger programs. By focusing on creating tests first, developers can find and fix problems early, make the code cleaner, and keep improving their work. Although starting with TDD can be tough, it’s beneficial in the long run for creating great software.

Using TDD means more than just writing tests; it’s about changing how you approach building software. A saying summarises it well: “Test the expected, code the unexpected. “ Let’s use TDD to create effective and reliable software over time.

References

--

--