Can you tell by looking at the unit test cases whether the code’s author wrote the tests first? During a TDD class I taught last week, one of the participants suggested that you can’t tell. Completed code and unit tests would look the same regardless of which had been written first. At the time I didn’t give the comment much thought.
My first stop was the Jasmine unit test cases. Sure enough, there was no test case expecting the expiration date to be later than the effective date. I added three cases — effective date earlier than expiration date, effective date later than expiration date, and both dates the same. It was straightforward to change the date validation function to make the tests pass. Fortunately the code is very clean, so there was no induced work due to design debt.
The student’s observation may be correct in some cases, but I think in general we can tell whether the tests were written before the code by looking at the tests, the code, or both. In this case, the Jasmine tests told me the code had been written first. I didn’t have to look at the production code to know that. The tell-tales were:
- There are no happy path cases. If the code had been driven by the tests — that is, if the test cases had described the functionality desired in the code, and then the code had been written to make that happen — then all the functionality would have been described in test cases, and not only the exceptions. Code driven by this set of test cases would never submit the input form, because there is no test case that drives that behavior. Based strictly on the test cases, the production code should do nothing at all except report invalid input values. Yet, this is not true — in production, the input form is submitted and the transaction is processed. Someone wrote the code to do that without reference to any test cases.
- The nature of the defect suggests the original author wrote the code first and the tests second. With his/her focus on the individual input fields, it would be easy for the author to forget to write validation logic that involved multiple input values. By describing behavior through test cases first, it would be hard to forget such a basic date range validation.
In this case, it was easy to see that the code had been written first by looking just at the unit tests. Writing the code first can also have a visible effect on the production code. Pairing on the same client’s back-end Java code recently, a team mate and I spent two days on a modification that should have taken no more than two hours. The code we had to work with had the following characteristics:
- Class with multiple responsibilities
- Long method
- Method with multiple functions
- Method operating at multiple levels of abstraction
- Reliance on side effects
- Tight coupling
- Embedded calls to static methods
- Deeply nested if/else structure with redundant tests of the same elements
- Hard-coded instantiation of collaborators
- Inconsistent naming conventions
- Duplicate and nearly duplicate code
- Stale comments
- Poor use of language’s type system — everything is a string
It’s unlikely the test cases were written before the production code because it would be very hard to come up with a concise test case whose simplest possible implementation is a massive hairball. Test-driving tends to result in a larger number small methods that perform just one function each, rather than a smaller number of large methods that perform multiple functions. You can often tell by looking at the code whether the tests were written before or after the production code.