Getting Started with Test Driven Development

Chase Stevens on January 18, 2019

Test Driven Development is considered to be a best practice in the software industry, yet many teams ship code without tests. What it i... [Read Full]
markdown guide
 

Although I believe strongly in testing, the TDD philosophy has never worked for me. I'm sure it is helpful for many others, so I would never go as far as to say "don't do it," but I would like to advise that one not embrace it blindly either.

In my 8+ years of programming, I've found that most of Uncle Bob's advice needs to be taken with a very large grain of salt, especially as he has a habit of conflating his personal opinion and emotional bias with fact, and then touting the result as the One True Way™. Time has proven over and over again that, in programming, "best practice" is a purely mythological beast. No practice or methodology is beneficial to every project; if something is helpful to one project, you can be certain it will be proportionally detrimental to another. TDD is no exception to this.

With that said, I do believe all projects require some form of testing, but there are dozens of equally viable methodologies and approaches to this.

Like I said, I don't want to claim that TDD is somehow "bad" or "wrong," but I want to caution that it is not a magic bullet, and its suitability to any project or team should be carefully and critically evaluated.

By the way, I detailed my own testing habits in a comment on this article (below), which is a great companion to yours, I might add. Some may disagree with my way of doing things, but I have written years worth of stable, maintainable production code, with surprisingly little technical debt. My methods work for me, and the proof is in the pudding. (Your pudding may well be different.)

 

Depends on the implementation, I write a lot of multi threaded code and functions which spawn threads to run control loops, they are very difficult to TDD, in fact, I would say, impossible! You end up writing tests that are more complex than the code itself and completely tie down the implementation, so they are not good tests. You can exploit encapsulation by exposing "hidden" implementation.

 

in programming, “best practice” is a purely mythological beast

This.

Even more, later in your great comment you state “there are practices that work for me“ which is IMSO the same biased attempt to invent a silver bullet—now among yourself during the time.

What works for me is I do TDD here and there and I don’t there and here. The task, the existing codebase, the current moon phase, the wind speed and my own mood dictate whether it’s TDDoable or better not.

 

Even more, later in your great comment you state “there are practices that work for me“ which is IMSO the same biased attempt to invent a silver bullet—now among yourself during the time.

Except I don't have any silver bullets there. The exact practice varies from one project to the next. ;-)

 

When I was programming in C#, using Visual Studio, NUnit, and NCrunch, we wrote TDD style unit tests. Not only did we have pretty good unit test coverage (about 70%), writing the tests was actually fun. Yes: fun. (Now only if I could put that in a 96 point font, with the blink tag.)

I attribute the fun factor entirely to NCrunch: it is pure magic. When writing TDD style unit tests becomes fun, unit tests get written.

These days I'm programming in C++. By-and-large, we don't have unit tests. We do have a few, but not many, using Google Test. Writing unit tests in C++ is not fun. There's nothing equivalent to NCrunch for C++. Google Test or alternatives like Boost Test Library are awkward to use, even though they are well thought out... they're still a bolt-on to the language and rely heavily on a lot of macro-magic to get the job done. But doing TDD style "write the test, run-and-fail the test, write the code, run-and-pass the test, refactor the code, run-and-pass the test, check-in" cycle doesn't work when the compile-then-run-tests takes 20 minutes. :sad panda: (C++20 will have contracts, which will help a lot. Funky syntax, though.)

One of my favorite languages is D. Two neat features of D is that it has contract support built into it, and unit testing built into it. Between those two features, I found writing the precondition contracts, postcondition contracts, and invariant contracts to be fun, and writing the unit tests to also be fun. Having contract support means there are a lot of simple sanity unit tests that do not need to be written, so the quantity of unit tests is a lot smaller. The syntax is very straightforward, and since it is part of the core language one doesn't have to get the team to agree upon some bolt-on unit testing framework.

For me at least, the upshot is: some languages that are much more amenable to TDD style unit testing. Other languages do not lend themselves to TDD style unit testing.

Unit tests (as a residual value) ensure basic correctness, especially for procedural and object-oriented programming wherein ensuring basic correctness is otherwise difficult. They are no substitute for automated integration tests and automated system tests. And, vice versa, integration and system tests cannot be used as a substitute for unit tests. Different domains, by different people, for different purposes. Unit tests are written by developers primarily as a design tool to aid development, with residual value as a regression suite to ensure basic correctness. (Regardless of TDD style unit tests, projects still need architecture. Unit tests are design-in-the-small.) Integration tests and system tests are written by quality engineers primarily to ensure the separate parts work together as expected.

PS: when I say "unit test" I mean that in the TDD sense. To me, things that are not TDD style unit tests are not unit tests, but the term "unit tests" is commonly used in the industry to refer to things that I would categorize as integration tests, or system tests, or performance tests, or security tests, or smoke tests, or usability tests, or acceptance tests, or bug regression tests (which are a usability tests variant). Best to be aware of the possible miscommunication due to same terminology with different semantics.

 

While I have given up on TDD, I have to say that with the Catch family of unittesting frameworks (currently I am using doctest) C++ has the nicest unittest experience for me. To the point that I do miss doctest when programming in python.

 

I've looked at Catch before. I like it, nice to see that Catch2 is still an ongoing endeavor. It is straightforward and easy to jump in and start using right away.

The single-header-file approach helps to lower the barrier to entry.

(I wouldn't use its BDD style syntax for unit tests. But that's just me, and it's a take-it-or-leave-it, so easy enough to opt-out.)

 

Firstly, TDD is universally misunderstood.

creating software by writing tests before writing code.

This is the most frequent misunderstanding. TDD is not writing tests then writing functionality. It is writing a test for the absolute simplest case, fixing that test then adding a test case. Therefore, testing is done alongside your code. I would have maybe accepted "creating software by writing a test before writing code." but "tests", no.

Take the example that you want to write a program that parses roman numerals.

  • You write the test myParser.parse("I") == 1
  • You write a RomanNumeralParser class
  • You add a method to parse
  • You write the functionality "if string.equals("I") return 1"

It's also important to note (as without it, it's not TDD) that Test Driven Development requires that

  • Your tests are unit tests
    • They are quick and standalone
  • You write the absolute minimal code to satisfy requirements
  • You refactor as you go

People who are against TDD have usually been exposed to it in the wrong way. It should be done correctly and strictly for a period of time, once it shows you how many assumptions you make in programming you then loosen how strictly you follow TDD. It's not a methodology to be followed, it's a practice to show you how to test properly and the benefits of fast, contained, independent unit tests.

 

There are a number of things that I find I don't like about TDD.

TDD is not about tests or testing, it is about design. But I don't like the design it promotes. Actually I have mixed feelings. The main complaint is that it can cause what would be a straight forward implementation and convolute it with abstractions and boilerplate.

What is nice is that the code can become easily exercised without full system execution. However this is good when the design chosen is pure functions, but all too frequently I see layers of mock and dependency injection frameworks used to manage dependency creation rather than dependency reduction.

D is such a blast to create compile time testable code.

 

My Ruby is a bit rusty, but it looks like the tests are testing whether the method returns true or not. What would it look like to test that the side effects executed as intended?

 

Can anyone please tell me how to follow a TDD when writing a rest API in express js ?

code of conduct - report abuse