
On Fri, May 27, 2011 at 12:42 PM, Frank Shearar <frank.shearar@gmail.com> wrote:
You miss the point of mocks. Mocks are there to allow you to test your code quickly and cheaply against things that may not work in a quick manner.
Not likely. Hernán has been around the block and he speaks from long experience and deep thinking on software design. If you read his post a little more carefully, you'll see that he distinguishes between test doubles in general and mocks as a particular form of test double. He uses test doubles - you don't get 23,000 tests to run in 7 minutes without them. It's just *mocks* that are mostly unnecessary. Personally, I find that mocks are *very* useful in a few very specific situations, but they can cause problems it other situations. They're like a very specialized tool - invaluable when you need it, but kept in the bottom of the toolbox and not used very often. Where I find them useful is in testing communication. For example, I'll use a MockWritestream to throw an exception if the code under test writes something unexpected into the stream. It's handy when testing code that writes out files in a particular format, or communicates with remote systems over the network. It might also be useful to test communications across formal module boundaries, where it's particularly important that a specific protocol be used - eg, for something like Prevalayer. My favourite testing pattern is TrivialImplementation. Instead of using a test double or an expensive "real" object, I often create a cheap/trivial implementation of the objects that collaborate with the objects I'm testing. For example, Monticello2 has several repository classes. There are implementations that store code in a single file, in a directory of files, on a remote machine via sockets, on a web server via HTTP etc. There's also a trivial implementation that just uses an in-memory Dictionary for storage. It's a complete implementation of the repository protocol, and Monticello can use it "for real", but it's not as robust as the other kinds of repository. All the repository implementations, including MemoryRepository, have a suite of tests that ensure that they work correctly. But when I'm testing other parts of Monticello that interact with a repository, those tests use a MemoryRepository. I know that BDD folks like to talk about testing behaviour rather than state, but I don't find the distinction useful. Testing state breaks the encapsulation of the objects under test, and couples the test too closely to their internal implementation. Testing behaviour using mocks *does the same thing*; it just restricts the implementation in a different way. I find it's better to give the implementation a degree of freedom by testing "results". Figuring out what result you're looking for can be difficult - it requires thinking about what a passing test really tells you. Here's an example - let's say we we're testing an implementation of Set. Here's the version that tests state: | set | set := Set new. set add: 3. self assert: (set instVarNamed: 'array') = #(nil nil nil 3 nil) Here we test behaviour: | set | mock := MockArray new: 5 mock expect: (Message selector: #at:put: arguments: #(4 3)). set := Set withArray: mock. set add: 3. I prefer this approach. It gives the implementation a lot of freedom, while ensuring that it does what we really want: | set | set := Set new. set add: 3. self assert: (set includes: 3). In short, I agree with Hernán. Mocks can be useful, but they're often overused. In most cases, they're unnecessary. Colin