- Last edited February 2, 2001 |
Last week, I was helping Bob Koss with a seminar he's working on. He was trying to talk about the use of stubs in testing. We kept coming back to the fact that we wouldn't do it. We chatted with Kent and others. Here's where I wound up:
I would use stub-type objects in testing if the tests ran horribly slowly without them. For example, accessing a database repeatedly, or accessing URLs.
I would use stub-type objects in testing if the real objects were very difficult to set up. But first, I would explore why the real objects were difficult to set up.
Stubs and Mocks introduce a new kind of potential error into the mix. When we write the real thing and the stub, we are responsible for making the semantics of the two interfaces the same. For a simple example, imagine a root stub where we tested that root 4 = 2 and root 9 = 3, and a real root routine where we tested root 8 = 2 and root 27 = 3. Both these tests run in good faith. It follows, of course, that the tests for the stub must be the same as those for the real object. In that case, why not use the real object in the rest of the tests?
My conclusion: avoid them when we can. And we usually can.
Ron Jeffries
I am also concerned about the example mentioned above - that doesn't look like a MockObject to me - what expectations are being set on this root object, or what values are being preloaded into it? This does not follow the format we specified in our paper. I could imagine a RootCalculator? mock object where you specified it should cause an overflow error or something of this type. In this case the test would look something like:
myMockRootCalculator.setOverflowError(true);This examples just checks for an exception, however in many circumstances there is an interaction with another model object, and again you would provide a mock object and set an expectation either that object is not called, or that an error condition is passed on eg.
try { myModel.calculateOptimumVelocity(myMockRootCalculator); assert("Should get model exception"); } catch (ModelCalculationException?? ex) { assert("Model exception expected",true); }
myMockRootCalculator.setOverflowError(true); myMockWorkflowReporter.setExpectedProceedCalls(0); myMockWorkflowReporter.setExpectedError(ModelCalculationException?);I think these tests prove to be extremely readable and they clearly communicate what is supposed to happen. If we find ourselves putting logic in a Mock we always question what is wrong (as we are duplicated code that is probably already in the model) and like Ron we look for an alternative solution -- TimM
try { myModel.calculateOptimumVelocity (myMockRootCalculator,myMockWorkflowReporter); ....
I agree with Tim, and the above example is almost there. What we'd be trying to test in this case would be that the exception is handled in the appropriate way, (not that it's thrown as we're forcing it to be thrown). And that is the point, we don't have to think of values that will make the calculator overflow, we simply make the calculator overflow.
myMockRootCalculator.setOverflowError(true); myMockWorkflowReporter.setExpectedProceedCalls(0); myMockWorkflowReporter.setExpectedError(ModelCalculationException?);It's not the overflowing that we'd be testing in this case, its what happens when it overflows. --OliBye
Result result = myModel.calculateOptimumVelocity (myMockRootCalculator,myMockWorkflowReporter); assert(result.isAnError());
- Last edited February 2, 2001 |