Freigeben über


TDD and mocks

First of all I must remind you that I don't like mocks and mocks aren't stubs. But recently I read a very interesting story on code without getters and its consequences. The story is about a company where the developers where asked to develop their (OO) code without any getters. The brilliance of this simple, and at first glance weird constraint is that it forces the developers to design the code in a way that is generally considered "better" (i.e. don't ask objects for data, tell them what to do). It also introduces the need for mocks in a natural way. Actually mocks are not needed. A fake or stub object might work just as well.

Let's take a look at an example. If we look at my example from I don't like mocks where I want to transfer funds from one account to another. Without getters the code would have to look something like this:

 transaction.commit(accountFrom.remove(amount), accountTo.add(amount))

If we use a mock object we can test that this code actually changes the account values correctly. But we still have the same maintainability and focus problems I discussed in the I don't like mocks post earlier. And you might as well use a stub or fake object. Actually the use of a stub and fake will force you to focus on testing the right things in your unit test because you have to test the account.remove and account.add methods by them selfs. And when you know they work you can fake/stub that unit completely and start testing that transaction.commit does the right thing given different behavior from the account objects.

Comments

  • Anonymous
    October 13, 2008
    Maybe I don't understand what you mean by a "stub" here. I don't see any return values, so what can be asserted over? With mocks, a test for commit would say only and exactly what it needed: that two different accounts are given two different commands with the same value. Note that we do not have to say anything about the order of those commands. In this way we go in the direction of decoupling, because now we don't need to check that accounts work properly as part of our check that commit works properly (or even that accounts are simulated properly with a stub). The use of mocks lets us say: assuming that accounts work properly, commit will work properly because this is how it uses accounts. Really good mocked tests don't have any assertions, which also gets us away from (writing tests about) managing state. Which is a win.

  • Anonymous
    October 15, 2008
    By stub I basically mean "a mock without knowledge of usage expectations", mor on that here: http://blogs.msdn.com/cellfish/archive/2008/04/22/mocks-are-not-stubs.aspx I agree that the use of mocks where we only verify number of calls and not the order is a much better use of the mock object than testing for order too. Reading the example I also see how you probably missunderstood what I meant. The code in the example is part of a method used for transfering money so the behavior being tested is actually "transfer money". Assuming we can tell the method what transaction and what accounts to use we can mock them all if we want. Since the behavior we're testing is "transfer money" and that is the public interface the only thing we can say from that perspective is that the transaction should complete and the accounts should have their balance change correctly. If we use mocks to verify that the transaction object gets commit called once and each account object have add/remove called once we are not only verifying the behavior of the interface, we're also verifying the internals of that interface. And the whole point with an interface (IMHO) is to hide the internals since they are not important. If I wanna do something like for(int i=0; i<ammount;i++) { accountFrom.remove(1); accountTo.add(1); } that should be my choice of implementation and should not be something I can't change because the mocks think otherwise. That is why I think mocks should be used as little as possible when you're TDD/BDD:ing. When you write your test/spec you should concentrate on what behavior the interface should have - not how the interface is implemented internally. The exception from this rule is when you want to verify a protocol os some sort where there are rules of the protocol to make calls in a certain order. And last, I agree that if you decide to use mocks you should not assert on state but rather on the (internal) behavior the mocks define. But that is something I personally don't think you should do for reasons described above.