Why “Test-First” is better than “Test-Later” Part 1

Thankfully, I have joined the training for Test Driven Development for whole week. That works shop changed my paradigm of software development.

One day, I met one of my former manager and we talked about the unit test. She also agreed the importance of unit testing and mentioned about timeline. To do the test driven development, she thought we need double time of the development. I also agreed with double development time estimate at that moment. Both of us were thinking writing unit test after the code implementation.
It was before I took the training. At that time, I haven’t had written unit test before writing code.

During the training, it gave me big shot and made me have big turning point of the software development.

  • Think through unit test
  • Write failing test first.
  • Refactor later.

While I was searching for articles to know what other people think Test-First vs Test-Later, I found good articles about this. I completely agree with the the owner of the article. I want to keep that information on my blog. (Please, take a look at the original link at the bottom)[1]

Tests exercise software to be sure it’s doing what was intended. So, whether you use Test-Driven Development (TDD) or write unit-tests after coding, you’re presumably getting the same benefit. The safety-net gets built, either way. Right?[2]

The difference between TDD and test-after unit-testing is subtle, but important. TDD is much more than writing the unit test first.

1. You cannot write untestable code.

This may seem obvious, at first, but what may not be so obvious is how easy it is to make unit-testing a bit of code harder by not writing the test first. If writing tests after were that simple, people would do it. And, they don’t, mostly.

  • Are you passing a non-virtual (sealed, or final) third-party dependency? How will you test all the permutations of interactions and responses with that object?
  • Did you create a method without a reasonable return value? (Note: Not all methods need a return value. Sometimes the most reasonable return value is none at all, aka void.)
  • Or (worse!) did you return an error code that the caller has to look up, or some object that the calling code now has to test against a null pointer?
  • Did you have a path that will throw an exception that the calling code has to deal with? Or (worse!) a Java “checked” exception that the client either has to wrap, declare as thrown, re-throw, or ignore?
  • Did you create a “helper” or “utility” method and, since it has no state, did you make it static? Yes, these are very easy to test. But their callers (typically someone on your team will be writing the calling code) are no longer easy to test.

Most (not all) developers tend to write perfectly reasonable procedural code when they don’t write the test first. The problem with perfectly reasonable procedural code is that it tends to be “scripty,” walking through a scenario and using branching to differentiate scenarios. Good procedural code makes for smelly object code, and bloated functional code.

TDD actually encourages us to write script code, but in the tests themselves. Each test is a representation of a possible calling sequence for client code. And it frees us up to design the implementation however we want, using the features of our programming language to its full potential: Objects, stateless functions, lambdas, abstractions, mix-ins, contracts, you-name-it.

2. You are recording your thoughts.

Objects and classes never live independent of each other. Instead, they combine and interact to create all the required behaviors. A “unit test” is a test of one tiny “unit” of that larger “business model” behavior.

The thought-process for TDD is not “Oh, let me write a test for my code…well how can I do that if I haven’t written the code?” Here’s a more common internal dialog: “I need this particular object to provide this next new bit of behavior. When I give the object this particular state, then ask it to act on that state, here are results I expect.”

Note that this internal dialog naturally occurs from a viewpoint external to the object. You’re recording a mini-specification for that new behavior in a simple, developer-readable, re-runnable automated test.

3. We design the interface from the viewpoint of calling code.

With TDD, often the required object class, or its methods, don’t even exist yet; so we’re effectively designing the naming and the interface (public method signatures) to that object through the tests. The unit tests are the first “clients” of that behavior. In this way, our interfaces (i.e., what objects are designed to do for other objects) are designed from the correct perspective: Each from the caller’s perspective.

4. You have to know the answer.

Computer programming does not do well with vagaries. Computers will do what you tell ‘em to do; nothing more, nothing less, and nothing different.

So if you don’t know for which day to journal a transaction that happens exactly at midnight, you’d better go find out! Ask the product advocate, business analyst, tester, or your pair-programming partner. If no one seems to know, then we need to ask an actual customer.

So we can’t write the test unless we know the answer we’d expect. You have to get out the calculator, slide-rule, or Google; or preferably the product advocate has told you what to expect (preferably through a Cucumber scenario).

What do we tend to do if we simply write out the whole 10240-bit quantum-encryption algorithm first, them write a unit test for it? We may assume the answer (which would look like random static anyway) our own code gives us is correct! And that, folks, is a huge disaster waiting to happen.

We’re more likely to look for the right answer before ever writing the solution to that request, than we are to ask questions after we’ve already written the code.

Footnotes:

[1] Dozen Reasons Why Test-First Is Better Than Test-Later (Pt. 1)
https://agileforall.com/a-dozen-reasons-why-test-first-is-better-than-test-later-pt-1/


[2]Lauri Williams did a study comparing test-first with test-after. The test-after team was finished before the test-first team. Alas, their code had more defects, because they didn’t sufficiently unit-test the code. In other words, they cheated. Not because they were cheaters, but because unit-testing code after the fact is harder…and boring. And you’re far more likely to miss an important case. https://collaboration.csc.ncsu.edu/laurie/Papers/TDDpaperv8.pdf

Leave a Reply