Trying to understand the value add of Test Driven Development (TDD)

I will preface the conversation by stating that I am not a practitioner, nor have I attempted to practice TDD in a professional setting.  This is a thought experiment vs a critic of TDD.  I also assume that the reader is familiar with the basic concepts of TDD.  Finally, one last caveat – my professional experience is in the embedded firmware space, e.g. microcontroller development.

My dumbed down perspective of TDD is that it offers the following advantages:

  1. Forces the developer to write automate units (whenever possible).
  2. All automated unit tests must pass before merging code.
  3. “A key benefit of TDD is that it makes the developer focus on requirements before writing code.” [1]
  4. You can “safely” refactor once all your unit test pass.

I 100% agree with the list above, however in my experience I don’t have to use TDD to accomplish all of the above. 

So the open question is: what I am missing about TDD? Assuming my statement is correct that you can obtain the benefits above without employing TDD.

My alternative:

  1. As part of your SDLC process, make it a requirement to have automated unit test whenever possible.  For many years now, for the projects I have led – we have had the requirement that all code require unit tests, or said another the unit tests are the tangible proof that your code works vs. “just trust me”.  In the embedded space not all unit tests can be automated when the code under test directly ‘touches’ the hardware registers.  However, that does not prevent writing a ‘manual’ unit tests that requires supervision when executing on the target hardware.  As an aside – I claim that over 80% of any embedded project can (and should be) tested using host based automated tests.  This is not hard (or time consuming) to achieve if it is part of the SW architecture and design from day 1.
  2. Incorporate CI/CD pipelines into the development process.  With GitHub this simply means that Pull Requests are not merged until the CI build passes, where the CI build includes building and running all automated unit tests.
  3. Separate the “design” activities from “coding” activities.  My upbringing goes back to the Shlaer-Mellor methodology in that coding is a translation step from your detailed design.  Or said another, solve the problem before you start typing code. And yes, this includes documenting the solution in a detailed design document. I advocate that Software Architecture, Software Detailed Design, and coding + unit tests are three distinct steps in the SDLC process (see my books)
  4. Once the unit tests exist (see item#1) – then yes, I can have confidence in the results of my refactoring because I have a known, proven existing test suite.

Final Note:

My personal test strategy, methodology, etc. is:

  1. Every sub-system, component, module requires a unit test.  And whenever possible the unit is automated test that can be incorporated into the CI/CD pipelines.
  2. Build and run your unit test(s) before you fully complete the coding of your sub-system, component, module.  The initial tests should focus on creating and initialization of the code under test.  This helps identify cyclical dependencies that are easily missed in your design.
  3. Once your basic unit test is up and running.  Iterate between coding, writing unit test code, and executing the unit tests.
    • Note: I rarely write the test code first.

[1] https://en.wikipedia.org/wiki/Test-driven_development