DEV Community

Thibaut Rousseau
Thibaut Rousseau

Posted on

TDD is a personal practice

I know TDD. I know what real TDD is. I know how to do it right, whatever that means depending on the TDD practitioner.
I still don’t like this practice, and rarely use it, except in 2 situations:

  1. I’m fixing a bug in an existing code base: in this case I’ll write a test first to reproduce and isolate the bug, and then fix it and refactor if needed
  2. I’m adding a new use case to an existing feature, and will treat the lack of existence of this new case as a bug, see 1.

I don’t ever, ever, EVER use TDD to write new features from scratch. It doesn’t work for me. It doesn’t match my mental model. It doesn’t help me go faster, doesn’t make me produce better code, and doesn’t make me write more, or more useful tests. I don't enjoy it and it doesn't provide me with any kind of satisfaction.

I still have a personal methodology, but it’s not TDD. I still strive to write good test and maintain good coverage, but not doing TDD.

TDD should not be forced upon developers who don’t want to follow it, in the same way a specific IDE should not be forced upon a developer. TDD is a tool, not a goal in itself. The shared goal is to have a well tested code base that is as safe as possible from regressions. How developers achieve that doesn’t matter.

TDD should not be conflated as "writing tests". I often see the rhetoric that, if you don’t do TDD, then you don’t write tests. If I showed you a code base with tests, there would be no way for you to know if they’ve been written in TDD or not.

TDD is not a team practice. Writing tests is a team practice. Code review is a team practice. Automated CI is a team practice. Whether a developer does or does not do TDD has no impact on the rest of the team. I could tell you I'm doing TDD if it pleases you, and you'd have no way to know that I don't, micro-managing set aside.

If in TDD you trust, don’t proselytize it. TDD is a personal practice.

Top comments (3)

Collapse
 
ben profile image
Ben Halpern

I agree in general.

To me, TDD is worth learning in the same way that lots of courses one might take in college are valuable knowledge even if the literal practices aren't used daily. TDD should probably be thought of in that sense.

I think because it's such a religious topic, I feel like it's not treated in such practical terms.

Collapse
 
githubmor profile image
Morteza Darzi

I think so , TDD is tools and use it when need it . but I think TDD is name for more generic tools that do best but when you want to use this tools should select one that proof your needs

Collapse
 
teamradhq profile image
teamradhq

Hmmm, I find the term TDD practitioner to be problematic in the same way Agile practitioner is. Agile is simply the philosophy laid out in the Agile manifesto. A bunch of non-technical people took the concept of Agile and turned it into a bunch of process and methodologies with complex (rigid) flowcharts determining how work should be delivered. Agile isn't something you practice, it's a philosophy that informs your work.

The same is true for TDD in my opinion. TDD is a philosophy that you apply to your work.

A few things I (personally) consider to be test driven development:

  • Writing some code, saving and then running the code (or refreshing a browser) to see if it works.
  • Using HMR for a front-end build system.
  • Writing a script to be run when files are updated, that will result in a success or fail state.
  • Running some requests in a .http file or a postman collection after making some changes.
  • Defining a test suite which encapsulates any or all of the above.

All of these things have something in common: they are a defined set of actions that I will perform to verify the correctness of an implementation.

I don't advocate for or against TDD. But I'm lazy and can't be bothered performing all of these actions manually every time I make one tiny change. I'd rather just define the actions once and then let the machine do it for me.

Take HMR as an example: so many front-end project I've worked on implement HMR but don't actually implement it.

You might see some call in the app index like this:

// index.ts
if (module.hot) {
  module.hot.accept('./some-module.ts', reloadFunction);
}
Enter fullscreen mode Exit fullscreen mode

Great! Every time I save a file the browser will reload and all state will be reset. The only module being replaced is the top level index.ts module. Everything else is thrown out and I have to point and click at a bunch of things to get back to the required state to actually verify the change.

In reality, every module should define some method for its replacement:

// some-module.ts
if (module.hot) {
  module.hot.accept('./module-dependency-a.ts', () => {
     // define how module dependency a should be replaced when 
     // some-module is updated.
  });

  module.hot.accept('./module-dependency-a.ts', () => {
     // define how module dependency b should be replaced when 
     // some-module is updated.
  });
}
Enter fullscreen mode Exit fullscreen mode

This is how you implement HMR, and it gets really tricky and verbose really fast, and after many years of dealing with these kinds of annoyances (and not being given the time to apply HMR effectively) it becomes much simpler to write some tests with a tool like Cypress, wdio or similar:

describe('some feature', () => {
  beforeAll(() => {
    cy.visit('/path/to/view');
  });

  it('should show some stuff', () => {
    cy.contains('Click Me').click();
    cy.contains('Some Stuff').should('exist');
  });
});
Enter fullscreen mode Exit fullscreen mode

It's objectively true that running this test allows me to verify the correctness of my implementation much faster than:

  • Alt tabbing to my browser.
  • Shift alt tabbing back to my browser because I have too many apps open (so many times)
  • Typing the path into browser address bar or locating and clicking a link.
  • Locating the appropriate control to interact with and performing the interaction.

In my experience, it's also true that this kind of tooling allows me to do a number of other things that would be very hard without automation:

  • Perform drastic changes in my implementation without worrying that everything will break.
  • Migrate some legacy code to [insert shiny new technology that a PM heard of so now we should rewrite our entire codebase to use it here].
  • Swap out one dependency for another.
  • Refactor and clean up my messy code before sharing it with my team.
  • Identify redundant and/or dead code (the only thing coverage reporting is good for IMHO).
  • Understand how different components in the system are expected to behave.
  • Verify that the business requirements (as I understand them) are implemented correctly.
  • Verify that stakeholder's acceptance criteria will be met by my implementation.

This last one is the biggest boon for me personally. Whenever I receive a QA test plan, my first task is to define these as automated tests. This frequently helps me avoid missing details in the acceptance criteria. If the QA test plan fails, then these tests fail, meaning the implementation is not ready for testing.

If you haven't read Kent Beck's Test Driven Development: By Example, I strongly encourage you to pick up a copy. He's the person who developed TDD originally, and you'll find that his opinion is much aligned to yours. TDD as a paradigm is just an approach you can use to verify correctness.

I'd challenge you to read the book, then attempt to cover the public facing facets of a project you're working on just for kicks. You'll find the process illuminating.

From the book's preface:

TDD is an awareness of the gap between decision and feedback during programming, and techniques to control that gap. "What if I do a paper design for a week, then test-drive the code? Is that TDD?" Sure, it's TDD. You were aware of the gap between decision and feed-back, and you controlled the gap deliberately.

That said, most people who learn TDD find that their programming practice changed for good. Test Infected is the phrase Erich Gamma coined to describe this shift. You might find yourself writing more tests earlier, and working in smaller steps than you ever dreamed would be sensible. On the other hand, some software engineers learn TDD and then revert to their earlier practices, reserving TDD for special occasions when ordinary programming isn't making progress.

If Kent Beck doesn't advocate for TDD first for everything then anyone who says so should probably read this book also :)