[Esug-list] Fwd: Re: Proposal for Mock Objects at ESUG 2011
macta at pobox.com
Sun May 29 08:31:25 EDT 2011
This has been an interesting thread - and it definitely seems that there is enough interested to warrant a further talk on the subject. I encourage Frank to pursue his idea to submit a talk (sadly I can't attend ESUG this year - which I am disappointed about. However maybe Frank and I can confer offline, and I can share some ideas)
As Neil pointed out (and more for background info) - I did present the ideas behind Mocks (and the library SMock, that was written for Dolphin) - back in 2007. I also did an update at Smalltalks 2009 and used the following screencast - http://www.planningcards.com/Thoughts/SMock%20and%20TDD%20in%20action.htm.
I have to confess that I keep promising to fully port SMock to Pharo/Squeak - and have done most of the work but I got a bit stuck with Monticello and how to properly do extension methods as well as getting diverted onto other things. It also felt that people weren't very interested in the technique so I was writing something more for myself and for researching how it would impact my Smalltalk coding experience. (By the way there is now much more documentation on how to use Monticello, and Pharo is so much better, and with our renewed energy in the UKSmalltalk meetings I'm more inspired to seek some help to get something checked in). It sounds like Frank also has another interesting library that we should compare (and since then I have augmented another internal Mock library on a JPMorgan project which approaches things in a slightly different manner). While SteveF mentioned that we can implement testing strategies in quite a small amount of Smalltalk code - the bulk of code actually comes in making test failures communicate clearly and explicitly as well as giving you a nice level of expression that keeps tests readable (I think these latter points are very important for long lasting, useful tests).
Having said all of this - many of these conversations do indicated a slight misunderstanding of Mocks and where they came from. To fill in a bit of background:
When I helped invent the approach it was very much driven from wanting to do TDD - and although it was originally written in Java it was inspired from my experience in Smalltalk. I am particularly fond of CRC cards - and the approach of "role-playing" objects and interactions (yet another Smalltalk invention). In our experiments with TDD in 1999, I found that our Java testing seemed "dumbed down - and we were often tempted to add "getter" methods to just test something. It seemed a far cry from Smalltalk and CRC - until we focused back on how to express that role play in our test code by not adding getter methods. Thus if you did take the time to CRC - and looked at who you collaborated with - then expressing that in a test became more interesting. "I am supposed to collaborate with the PayrollCalculator - and if its the end of the month then I should ask it to #calculatePensionFor: employee on: date for every employee in my dataset. If I get the wrong date, or don't ask about every employee then I should fail the test." We found that if you could focus on encoding the CRC session in your tests, that you got better tests and better code, as well as better test failures. In Java it felt like you got code that was more Smalltalk like.
So I guess I would comment that for us, Mocks were different because we wanted to focus testing on asserting relationships and interactions and less on data values. I read all the literature at the time (there is a massive book on Testing on my bookshelf from that time) - and nothing else seemed to have this same focus (stubs felt more data related). As we played with this more and more, we refactored out useful techniques to ease the assertion of relationships, and someone coined the term "Needs driven development". That is, the act of thinking in this way made you want to have relationships that you could assert against. For me - these were the "interesting tests" that we had been writing back in Connextra that felt better than the others.
I think this also came out of our experiences of using TDD - and the observation that when you write tests for objects that you consider low hanging fruit (because you know you need them, and they seem easy to test) - invariably you make the wrong decision and test that something threw an exception instead of returning a boolean (or better still in Smalltalk, has an ifAbsent: style construct). Of course you only find out about this when you go to plug your well tested object into the place where you need it and discover that if you had just returned true/false then it would be much easier to use. So you go back, rejig the tests and you get a bit further until you discover another slightly wonky decision. After doing this a few times I made a point of saying to my team mates, lets always start with the top most object and write tests that help us learn what these smaller pieces (which we know we need) should actually do. This resonated well with CRC and really brought everything together for us. This is how MockObjects were born (and I asked Steve and Nat if they would like a chapter in their GOOS book about this history - and they happily included it as an afterward at the back).
So coming back to Smalltalk - I still would argue that this CRC technique is very much alive, and I personally still find that "Needs Driven Development" is a useful technique. However it does mean that you get into a chicken and egg situation. If you start testing with a high level object - how do you test or get something working when you are referring to things that don't yet exist? For me, thats where Mocks step in - they are intended to be light weight proxies for things that don't yet exist. You do a CRC session, pick out the highest level object you can - code it (with tests) and mock out the relationships that you recorded on your CRC cards. As you are coding it, you will probably change your mind (writing real code has a way of doing that) - and you modify your lightweight relationship assertions (mocks). This also works best and is easier if you delegate responsibility and avoid returning complicated things (its easier to Mock and happily conforms to the law of Demeter).
When you are happy enough with your top level object - you can then pick on one of those other relationships, and your tests should now show you the "Protocol" that this new object should have. So you create it - and repeat the process. The interesting twist that the screencast I linked to demonstrates - is that in Smalltalk (and indeed in Ruby or Python) we can create MockObjects that don't have any code, however you can use all those assertions to actually generate an object ready for you to test it. I think this is an interesting idea that needs more research (I feel that the right assertions could generate interesting code).
As a final observation - when people talk about TestDoubles and MethodWrappers, I think they are missing the point of mocks as I intended them - as you can't double or wrap something that isn't yet written? So I would say that these techniques are perhaps better suited to code that is already in existence. Of course once you have done TDD with Mocks, and written your code you are then in a place where you can decide what to do? For me - I have kept the mocks in place - however I think in Smalltalk there is possibly a useful "Lint" check that makes sure your mock stays in sync with the underlying code (I haven't written any Slime style rules - but I think it would be interesting). There is also a useful hybrid functionality where you only mock certain methods and pass control to a real object underneath - or "method wrap" and decorate assertions you want on existing methods and pass down to the functionality underneath. I would argue that these are possibly more "maintenance" or "integration" style endeavours - and it would be a shame to not use the purer "Needs driven development" style when you can.
Anyway - I hope this is a useful background and might inspire more work in the area.
More information about the Esug-list