Today in class I got to understand, for the first time, what I define as “the simplest thing that could possibly work” when doing TDD. Many people struggle with the idea of simplicity. the idea of doing things to the point of “micro-increments” to the code, that will then help in driving out new tests that you had not thoguht about, is really tough.
what’s missing is a simple set of rules you can check your implementation against.
here’s a set of rules to serve as the basis for this discussion. I’m sure there are more, and i’m sure this will change over time, as defining this “simplicity pattern” is a rather new thing for me. I have not seen this done elsewhere.
the situation:
- You had a failing test
- You went to the production code, and made the test (and all other tests) pass, in the simplest way you thought possible.
- But was it really the simplest way?
look at the code you just wrote in your production code and ask yourself the following:
“Can I implement the same solution in a way that is ..”
- “.. More Hard coded ..”
- “.. Closer to the beginning of the method i wrote it in.. “
- “.. Less indented (in as less “scopes” as possible like “if”s, loops, try-catch ..”
- “.. shorter (literally less characters to write) yet still readable ..”
“… and still make all the tests pass?”
if the answer to one of these is “yes” then do that, and see all the tests still passing.
the trick is this: by doing this, you’ve implemented the same feature (or part of feature) and yet you knowthat the feature is not complete yet. Something is missing. it might be too hard coded now, or not in the correct scope or it’s missing a check.. but all the tests are still passing, so you can’t change the code now(in tdd you can only change production code functionality when you have a failing test)!
what do you have to do to be able to change the code?
Add a test that proves that something is missing in the code!
sometimes, you might discover that the simpler way is actually a good enough solution that does not need changing, or any more tests.
either case, you’ve won – you either got a simpler solution to the same problem, or you discovered a test that was missing.
Example:
say, that, you had a new requirement to add a call to a logger when a login succeeds for a user. I won’t show the test that failed, just the code that you might have written to make the test pass:
You added one line of code, that calls a logger with the string containing the user name and a short message.
Now, let’s see how we can make this implementation simpler, without breaking any tests:
1. can I make it “.. More Hard coded ..” without breaking tests?
YES (I only have one test)
assuming the test was trying to log in with a user names “testuser” i can make the test pass by hardcoding the user name. this would force me to write another test that sends in a different user name to the method, that proves this cannot be a hard coded string.
2. can I write it “.. Closer to the beginning of the method i wrote it in.. “
YES. Assuming there is no test that assume logger is called otherwise from this method, I can do this easily without breaking the test. I’ll just move the code up to the start of the method:
3. can i make it “.. Less indented (in as less “scopes” as possible like “if”s, loops, try-catch ..”
YES. I already did, in the previous step.
4. can I make it “.. shorter (literally less characters to write) yet still readable ..”
doesn’t look like it.
now, I have this code, and i know it is not good enough. I need to prove that using tests before I can change it, which forces me to think of two new tests: one that logs in with a different user name, and another that makes sure the logger is not called unless the login is successful.
Are these tests valuable?
I’m not sure. Do we really need a double test with two different values? I’m partial to this.
Do we realy need a test that proves something doesn’t happen? not crazy about it. it feels a but like over specification – why should i care if the logger is called or not in a place I don’t care about in terms of functionality? that test would break the second someone would add logging there, which is obviously needed, so it might just be a test waiting to be deleted.
on the other hand…
doing things so incrementally is a god way to teach small, incremental steps. as students become proficient, they can choose which increments to skip, but these small steps are good for “muscle memory” during kata training.
should i really forgo these increments (therby loostening up the need to “micro-increment” and giving up muscle learning) in favor of teaching types of tests that arn’t needed? or is muscle memory important enough to “sacrifice” some robustness in a few tests (which is also very very important).
which value is more important? micro-increments? or recognizing un-needed tests? which one is easier to learn later? it seems micro-increments (the way i teach) require writing some tests that may feel un-needed. but do I really want to let go of forcing this very powerful habit?
I’m so sorry to announce this. Due to various flight delays coming in to Oslo from Tel aviv, I am stuck in munich, only coming into Oslo tomorrow afternoon. That means that if you are part of my TDD course starting tomorrow, tomorrow’s day is canceled.
Instead the following will take place:
- Class starts Tuesday morning at 8AM (instead of 9AM) and will begin so for the rest of the week at 8.
- We will try to finish days a bit later (though I doubt of the effectiveness – it’s a tough class and at the end of the day everyone is beat). We’ll see how this goes.
- We will see about adding a half day or maybe more on Saturday if the class wants to. I will delay my return flight home to make this happen.
Again, my apologies – these are unforeseen circumstances, but for next time I will be sure to pick flights that wil have less risks like this.
a friend came up with a good way to describe good tests the way I see them (in my book I describe good unit tests as ones that are readable, maintainable and trust-worthy at the same time).
He just called them RTM: Readable, Trust-worthy, Maintainable
I think I’ll keep this idea – as it flows well with the idea of an RTM application – can you actually “release” these tests to the market (that is – for someone else to read, maintain and trust)?
Having seen enough crappy unit tests to last me a lifetime, I think there should be a parallel to the old “red-green-refactor” (from TDD) –
it’s evil twin brother should be “Brown-Green-Refactor” wherin you’d do a crappy test first, then make it pass (it could pass for the wrong reasons.. like threads, randoms, dates..), then refactor – the code, not the test.
in red-green-refactor you’d write a good test and remember to also refactor the test when refactoring your code, if needed.
It amazes me that people still feel the need to solve things in the hardest way possible simply because it is “free”. In this thread, a guy wants to setup an automated build for his company a a demo of how good it can be, and the proceed to dive into many rabbit holes as possible in his quest to show how simple and good such a solution could be (thanks CCNet and XML – looks like you’re here for good)
wanna show people who simple and clean it is to have an automated build? Get TeamCity running along with FinalBuilder (and you can hook them up to TFS if you like) – get this all running in an hour, and you have a winner.
Oh, but it will cost money? You already saved that money by *not* working for two days on setting this same thing up using the various, shall i say, less-than-maintainable-unless-you’re-ayende tools.
granted – if there *were* indeed tools that come close interms of usability and maintainability to those two in the open source or free world, I would recommend them, but you know what – there arn’t any that don’t require you, as a cost of entry, to pay up front much more than what the “paid” tools cost. and even then, they are not there yet.
Prove me wrong by showing me a UI as usable and maintainable as that of FinalBuilder in the free worlds, or as TeamCity. I’d love to know!
Corey Haines recreated my TDD Kata on his KataCasts blog. (click here to learn more about TDD katas)
Since Corey is an experienced TDD practitioner I was honored that he’d given it a go, and was eager to see the video. Later, corey asks for my remarks on it, which i will gladly put here. In general, Corey’s video is a great piece of TDD work to watch as an example of a master TDDer doing his thing. Everything flows gracefully and the code produced is simple, readable and of course – works.
I especially loved the large amount of merciless refactoring Corey made during the Kata. As you TDD you should not cut any corners in the refactoring area – and almost after every passing test or two, Corey does refactoring to some very simple code patterns.
Another worthy note – Corey takes care in refactoring the tests as well throughout the kata. Kudos!
A few things that stood out for me can be thought of as very little things – and are mostly questions:
- Partial Test: at some point, Corey seems to be writing the tests incrementally as well, in that he writes an empty test that passes, and then fills in the test itself to see it fail (which is usually where I start). I wasn’t sure why he’d do that, but the reasoning could be that he wants to make sure the environment works for the test. since he does it all in Ruby, which is a more forgiving environment for typing errors, this might be it.
- Always passing test: very early on, in the second test actually, Corey creates a test that asserts that sending “0” returns “0” – which passes and never fails. That is because by default empty strings as input return zero. I do it a bit differently – i try to start with a failing test that drives more functionality, and if i can find a way to make a test for “0” fail as well to being, i will do it (so that i can ‘test the test’ in TDD style).
A different way: Don’t write a passing test for “0” input. instead write a failing test for “1” input. Another options would have been to indeed start with a passing test for “0” input, but then to go production code and deliberately return –1 to see the test fail, then fix it to return “0”, see the test pass again, and move on to the next test.
- Magic inputs: Corey sends in various seemingly random, out of the air numbers as inputs to the tests starting from the third test onwards. For example, to a reader who sees a test that says “sending in 5 returns 5” that reader might ask themselves “why 5? why is 5 so special? should I also use 5 in my tests?”.
A different way: To avoid such confusion i like to stick to the lowest common version of an input that still proves the application wrong. if sending in “1” produces the same result as sending in “5” then my all means i will send in “1”. If the input is 2 digits, I’d send in “10”, “100” and so on. If the input still does not make sense I would put it in as a variable named “SINGLE DIGIT NUMBER” to explain to the reader what is so special or not so special about this value.
-
The simplest Test:Corey wrote the tests for inputs with \newline characters without considering the most basic test for this kind: a simple string containing only a new line character (equivalent to an empty string). I agree that not everyone would pick up on this as an idea, since you might feel this is an invalid input into the system. nevertheless, when I teach TDD this is the way i teach the kata. It feels like a more natural incremental evolution of the code and tests, and a good way to send the message that anything can be divided into small increments if you want to.
-
Refactor to un-readability: At some point Corey refactors the code that handles delimiter characters into a single line of Ruby code. while it isa form of refactoring, i found that the code after refactoring was much less readable than it was before.
A different way: I try to avoid any “immediate if”s or single line “smart” statements that feel more “smart” than “simple”. When i have to choose between length and readability, i will choose length every time. this makes sure the reader is always comfortable with the code.
Summary:
These small things aside, I think Corey’s video is a great example of TDD in practice! Go watch it.