Fake It

One of the GreenBar TestingPatterns from KentBeck's TestDrivenDevelopment.

If you have a broken test, the first priority is to get the test to pass. Since there may be some effort required in making the code really work, fix the problem by inserting a fake implementation, such as one that returns a "known good" answer (e.g., a constant value). Afterwards, once the test has been satisfied, go back and fix the implementation.

Surely there is a better solution - some way of deactivating the test, rather than trying to fool the testing framework?

This pattern is a way to force you into writing comprehensive tests. If your test calls for a single value to be returned from the method, then make your method return only that single value. This verifies that the test works as expected. Next, introduce a new test that tests for different inputs and results. The "return a constant" solution is no longer the SimplestThing, and you will need to move a step closer to the actual solution. It encourages you to not try to deal with every possible special case in your implementation unless you've written tests for the special cases first.

For example, with the following test:

  def testFactorial
    assert_equal(120, factorial(5))
  end

It might be tempting to jump straight to the solution of:

  def factorial (input)
    raise "input cannot be negative" if input < 0
    return 1 if input == 0
    return input * factorial(input - 1)
  end

Which has left me with a bunch of code that hasn't had tests written for it yet. I may know that it's the right solution, but I don't have any tests to support me in that assumption. Using a constant-returning function to "fool the test" encourages you to write your tests in a way that cannot be fooled - it guides you to think harder about your tests, rather than thinking about your code.

Of course, if you take this approach too far, you'll end up with all your methods looking like this:

  def factorial(input)
    case input
      when 5 : 120
      when 3 : 6
      when 0 : 1
      else raise "input cannot be negative"
    end
  end
So, like all good things, use in moderation.


If you refactor the above into the more clear:

  def factorial(input)
    case input
      when 5 : 5 * 4 * 3 * 2 * 1
      when 3 : 3 * 2 * 1
      when 0 : 1
      else raise "input cannot be negative"
    end
  end

Then it becomes more obvious that you're failing the OnceAndOnlyOnce test.


I'm not used to writing unit tests; if the above test with 120 isn't good, how would you test factorial? I want to see more examples of unit tests.

The 120 test is bad because it can be made to pass with this implementation:

    def factorial(input)
        return 120
    end

A better test would test a few more values, and should particularly cover some odd cases like 0, &c..


Being young and unexperienced and all that, this pattern (don't fix the code, just make it pass the test) strikes me as utter crap. After all, you want your code to implement some sort of specification (be it in written or unwritten form, formal or informal) - the tests exist in order to give you an indication whether the code actually does what it should do. If you purposefully fool the tests, you're in trouble. "Afterwards, once the test has been satisfied, go back and fix the implementation." That seems dangerous - you might forget that you still have to fix it. Besides - why not fix it in the first place? "If your test calls for a single value to be returned from the method, then make your method return only that single value." That's not the SimplestThingThatCouldPossiblyWork - because it doesn't work! It's rather SimplestThingThatWillMakeTheTestsPass?.

I just don't get it.


I know what you mean, and until a few weeks ago I'd have said the same thing. What you're missing is that (i) the tests *are* the spec, and (ii) as you write more specific tests, and the test-passing implementations, you'll find yourself generating more specification (ie tests).

In general, it's not possible to come up with an only-as-much-as-necessary-but-nevertheless-complete spec in the absence of trying to implement it, and TDD addresses this head-on by making the two activities part of the same process.

I found that no amount of being told this made any difference - I had to try it out on an example a bit more complex than factorial(x) - eg sorting a collection of strings. I learnt the hard way not to write lots of tests at once, and instead let the process drive them out.

Like you, perhaps, I felt positively angry at not being 'allowed' to just write what I felt I knew was the full implementation. This led to my biggest realisation - that hitherto programming had been more about massaging my ego than solving the problem at hand.


CategoryTesting


EditText of this page (last edited June 19, 2010) or FindPage with title or text search