Literal Laws

My friends, the code gods were watching me last week and filled me with the providence to contemplate test-driven development at just the right time to prepare me for an inter-team bru-ha-ha. The clarity I gained from T.I.N.O. was put to immediate use when explaining to another team why we don’t practice TDD exactly. Objects, I posited, and still posit, are best designed wholistically, because objects rarely exist in a vacuum.

That is the party line here on the Excellence In Programming web-ring, but that doesn’t mean we move on. No. Of course not. Let’s assume that we are total believers in dogmatic, hardcore TDD. That means we follow the three laws:

  1. You are not allowed to write any production code unless it is to make a failing unit test pass.
  2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
  3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

We are about to write a little service class that retrieves a data structure from the database, but we will strictly follow the three laws. Pay close, special attention to law #3. I want to see if I can arrive at the same code following these laws, especially law #3, as I normally do by my wholistic, top-down, design-by-code method.

First the test (law #2):

And the first failure, which happens to be a compilation failure (by law #2):

By law #1 and #3, I only write as much to get rid of the compilation failure:

By law #3, I instantiate to get rid of the NullPointerException:

Add an assertion and re-run (law #2):

Ditto (law #3):

And now a new failure:

Law #3:

The first pass!

Now moving on to a new test (law #2):

By law #1, I am allowed to write production code, and I write only as much as I need to pass (law #3):

Green!

Starting again with more tests (law #2):

And now a critical point as I follow law #3. Uncle Bob notes that as tests get more specific, the code evolves to become more generic because the code will eventually get smelly. Developers, he says, must recognize this point during TDD and code accordingly.

Remember, I’m following law #3 to the letter:

And it is enough for a green bar:

Circling back to law #2, I notice that my requirements state that this objects needs to retrieve the names of these data structures, so I add a test for that.

Law #3, gotta love law #3:

You can see where this is headed via law #2:

This time, we can’t just barf back the arguments because the name attribute, by our requirements, isn’t an argument. It is somehow figured out by the object we are writing. By law #3, it is figured out in the way that requires the least amount of code possible:

Law #2…

…and law #3:

At this point, I will stop and ask the burning question: what test can I possibly write to get to where I know I will need to be?:

Moreover, imagine that DAO. Following the laws to the letter, how could you possible write a test that will verify the parameters of the stored procedure are correct or that the DAO actually makes a call to a database connection (which you will subsequently mock out)?

Before I talk about this, we need to poll a group of people who are far smarter than I.

Helltime-repeatant Mark Needham has blogged in the past about using TDD on delegation. However, the post is mostly about unit-testing such objects, not writing the tests first in order to derive the code. Jay Fields addresses TDD and delegation in a Ruby context but also fails to follow the laws as literally as I have.

Jay and Mark, how did you follow law #3 and arrive at your implementations?

Back to Uncle Bob himself, a TDD example on a bowling game showed such promise. It started out with a very rough class diagram before the tests. So this made me believe that maybe this entire methodology needs a design to get started, and law #3 is followed with this in mind. This is even supported by a video tutorial on TDD by Przemyslaw Bielicki.

Przemyslaw, how did you follow law #3 and arrive at your implementation?

But later in Bob’s presentation, when the code and the tests are written, none of the collaborating objects are used at all. Another Uncle Bob example is the Prime Factors Kata, for which he is rightly criticized by Krishna.

According to refactoring god Martin Fowler, my tendency to design from the top downwards makes me a mockist who practices need-driven development:

With this style you begin developing a story by writing your first test for the outside of your system, making some interface object your SUT. By thinking through the expectations upon the collaborators, you explore the interaction between the SUT and its neighbors – effectively designing the outbound interface of the SUT.

Once you have your first test running, the expectations on the mocks provide a specification for the next step and a starting point for the tests. You turn each expectation into a test on a collaborator and repeat the process working your way into the system one SUT at a time.

But, but, but he glossed over the big question! During the course of dogmatic TDD, how do you force the introduction of collaborators into the topmost object’s implementation?

Martin, how did you follow law #3 and arrive at your implementation?

Seriously, can I not find someone who has addressed this question? Every example seems to be overly simplistic. Brian Button swings and misses but says “Care must be taken when crafting test data to have it force you down the path you need to take”. I don’t see how this would possibly force the object you are writing to use other objects, as opposed to not. What about your test data would illuminate a light bulb above your head and make you say “Ah ha! I need to delegate this call to a DAO for this test to pass!”?

Someone has at least asked about ways TDD sucks, which I’m starting to believe. The top answer supports a more moderate reading of law #3:

Sometimes the design is not clear at the start and evolves as you go along – this will force you to redo your test which will generate a big time lose. I would suggest postponing unit tests in this case until you have some grasp of the design in mind.

My theories

Like most things around here, I think this will fall to me.

  • Perhaps the database is an “input”.

    Perhaps it is implicit, or hell, maybe explicit, in our requirements that the names (in our example) must come from the database. Therefore, since our tests are driven off of the requirements, somehow our tests will make sure that the service object is making use the database. Good design dictates that this is factored into its own class, and we go from there.

    But still, I want to see what that test looks like.

  • Perhaps TDD doesn’t find this glaring bug and it is left for integration testing.

    Yeah, ok, but I haven’t ever heard anybody ever claim such a deficiency. But I could understand this. If I declare the service object done the way it is, it will get kicked out of integration testing faster than liberal raises taxes. Oh! Oh! I just thought of this: is there such a thing as using TDD in an integration testing setting? Now that would let you write your code the way you need to and at the same time, not write more than you need to get the test to pass.

  • Perhaps TDD is only useful for objects without collaborators.

    I list this theory because it is what the crappy resources on the web lead me to believe. For Christ’s sake, people, if you are going to tout this technique to all of our idiot managers and scrum-masters who are merely looking to inflict the latest buzzword they heard at a conference onto their developers who are actually trying to get the work done, then at least provide examples that are applicable in the real world, even if that means it won’t fit onto your PowerPoint slide.

  • Perhaps my own example isn’t realistic enough.

    This gets back to Uncle Bob’s point of “specific tests, generic code” and judging when you need to refactor. It is possible that the way the service object ended up would be smelly if I was dealing with a real object that had more than two data attributes.

    Truthfully, the service is doing two things: creating instances of the data structure and looking up the data. You could argue that the code stinks and extract these two responsibilities.

    However, how many tests would it take to force your hand, and if you already know where you’re going, aren’t these extra tests bordering on overhead? If I already know I’ll need a factory and a DAO to get the real job done, why can’t I go straight there?

    I’m starting to believe that in order to do TDD effectively and by the letter of the laws, you have to be a refactoring maniac. Otherwise, you will end up with the a retrieval service that doesn’t actually retrieve anything.

    I suppose this could also work if I applied by-the-letter TDD to the DAO, but wouldn’t the process just recurse forever? I’d write a DAO that had a lot of hard-coding in it, realize it was doing more than one thing, extract a class, and then start writing that extracted class. Wouldn’t the cycle continue forever? At what point would I break down and say “OK, I need to write some JDBC here to connect to an actual database.”?

  • Perhaps Law #3 is gunk and isn’t to be followed to the letter.

    If you know what reality is and will be, then you amend Law #3 to read as follows:

    3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test and is commensurate with the overall pre-meditated design.

    Yeah, intentional vagueness works both ways, bitch.

None of these theories is satisfactory if I am to follow the three laws to the letter, as the Highly-Paid Consultants claims is super easy to do in the enterprise development world. I will simply accept that I don’t get it. I don’t get hardcore TDD. Somebody tell me: what am I missing?

Announcer: You’re reading the EIP web-ring.

One Response to “Literal Laws”

  1. Have you read: “Growing Object-Oriented Software, Guided by Tests” by Freeman and Pryce ?

    The authors describe the outside-in or mockist approach. Perhaps the book can answer your question about thinking about collaborators at the highest level.

    TDD has been shown to fail in some situations such as deriving a Sudoku Solver. I think that some (possibly most) problems benefit from a bit of big-picture thinking. TDD can then be used to drive the object interactions and interfaces in detail.

Leave a Reply