Re: [Esug-list] Proposal for Mock Objects at ESUG 2011

CP
Colin Putney
Fri, May 27, 2011 9:11 PM

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

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
GK
Göran Krampe
Tue, May 31, 2011 12:43 PM

Hey!

Stumbled into this thread but just HAVE to respond... Snipping heavily here:

On 05/27/2011 11:11 PM, Colin Putney wrote:

without them. It's just mocks that are mostly unnecessary.

Amen! I have been thinking the rest of the world had gone mad...

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.

Right.

[SNIP of good stuff]

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.

So refreshingly to see someone that also notes that testing using
mocks actually break encapsulation. Thank you, thank you...

regards, Göran

Hey! Stumbled into this thread but just HAVE to respond... Snipping heavily here: On 05/27/2011 11:11 PM, Colin Putney wrote: > without them. It's just *mocks* that are mostly unnecessary. Amen! I have been thinking the rest of the world had gone mad... > 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. Right. [SNIP of good stuff] > 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. So refreshingly to see someone that *also* notes that testing using mocks actually break encapsulation. Thank you, thank you... regards, Göran
FS
Frank Shearar
Tue, May 31, 2011 2:59 PM

2011/5/31 Göran Krampe goran@krampe.se:

Hey!

Stumbled into this thread but just HAVE to respond... Snipping heavily here:

On 05/27/2011 11:11 PM, Colin Putney wrote:

without them. It's just mocks that are mostly unnecessary.

Amen! I have been thinking the rest of the world had gone mad...

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.

Right.

[SNIP of good stuff]

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.

So refreshingly to see someone that also notes that testing using mocks
actually break encapsulation. Thank you, thank you...

Or to rephrase, "documenting the precise nature of the colloboration
between an object and its context documents that precise nature."

In Colin's example, Set's collaboration with Array is purely an
implementation detail and, as he notes, testing the implementation -
whether checking that Set stores the value in the array, or what
messages Set might send to Array - hurts you.

I fail to see how that makes mocks bad. Unless you're railing against
hype? In which case, I agree: there ain't no such thing as a free
lunch, there is no silver bullet, etc etc.

frank

2011/5/31 Göran Krampe <goran@krampe.se>: > Hey! > > Stumbled into this thread but just HAVE to respond... Snipping heavily here: > > On 05/27/2011 11:11 PM, Colin Putney wrote: >> >> without them. It's just *mocks* that are mostly unnecessary. > > Amen! I have been thinking the rest of the world had gone mad... > >> 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. > > Right. > > [SNIP of good stuff] >> >> 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. > > So refreshingly to see someone that *also* notes that testing using mocks > actually break encapsulation. Thank you, thank you... Or to rephrase, "documenting the precise nature of the colloboration between an object and its context documents that precise nature." In Colin's example, Set's collaboration with Array is purely an implementation detail and, as he notes, testing the implementation - whether checking that Set stores the value in the array, or what messages Set might send to Array - hurts you. I fail to see how that makes mocks bad. Unless you're railing against hype? In which case, I agree: there ain't no such thing as a free lunch, there is no silver bullet, etc etc. frank
DS
Dennis Schetinin
Tue, May 31, 2011 8:02 PM

First of all, I'm sorry for my English. (And I'll appreciate it very much if
someone will point to mistakes of any kind in the following text.)

I attempted to analyze and structure comments, questions and ideas presented
in the thread and I think I found one thing that can be a starting point for
the discussion (despite the fact the discussion already started… away back).

I've got a strong feeling (I've got it several years ago and had a number of
evidences, including several in this thread) that there are (at least) two
very different (if not opposite) points of view on mock objects… and it
seems to me, the root of this divergence is even deeper and more general —
understanding of TDD. So, I think I have to explain my point of view.
(Telling "my" here I claim to be an author of all errors, misinterpretations
and nonsenses I'll present. But all the good, reasonable and interesting
things were invented by other (clever) people.)

So, why and how did I dare to claim TDD is not really popular among
Smalltalkers? Because I still believe tests can drive (nearly) all stages of
development: not just coding, but also design and even analysis (at least to
clarify and formalize results of analysis). But I rarely (if ever) see any
artifacts (tests of the kind). Perhaps I'm wrong, but most of the tests I
saw were more "acceptance" tests, perhaps even written afterwards. Once
again: very likely I'm wrong (and I'll be glad to be, actually).

Why is it important for me to use TDD? Because, as a bad programmer, I need
a help. I have a lot of things I must think about during development (won't
enumerate, we all know them), so I want a tool that helps me not to think
too much — at least, at the points where it is possible at all. (If I
misinterpret the word "driving" here, please let me know.)

Thus, if I write a line of code that is not directly(!) driven by a test, I
don't TDD. If I make some up-front design I'm not actually test-driven.
Even, if I simply go few steps designing top-down without tests at a very
small single stage of development, TDD is broken. If you think it is too
extreme and strict — ok, let's talk about "seamless TDD" vs., say, "partial
TDD". (Yes, I know about steps of variable size during TDD etc. But I still
think it's a bit another thing. Let's judge a bit later…)

So, considering an example by Colin Putney:

| set |
set := Set new.
set add: 3.
self assert: (set includes: 3).

vs

| set |
mock := MockArray new: 5
mock expect: (Message selector: #at:put: arguments: #(4 3)).
set := Set withArray: mock.
set add: 3.

Isn't it obvious mocking array here is an overkill and the first test is
better? For sure it is.

But why? Because there's actually no development in the 1st test. Otherwise,
what is it for: #add: or #includes:? Which feature this test makes me to add
to the system-under-test (the Set)? I must select, I must think, thus I'm
not driven by the test.

Consider also (slightly) another issue: here we have a ready-to-use inner
collection for the Set. But if I have no yet? Isn't it a common and very
frequent situation during development: I have to implement a (sub)system
that comprises several objects working together? (I think I don't have to
provide examples here to prove.) Which one should I implement first? I don't
know, I have to explore. I must analyze the system, i.e. divide it into
subcomponents, find the ones independent and start by implementing them
first.

But what is this process, I've just described? It's a top-down up-front
design. Is it test-driven? Obviously, no. Do I have an alternative? Yes, I
can use mocks. With Mocketry I could write:

SetTests >> testStoresElementsInInnerCollection
[:collection |
set := Set withArray: collection.
[set add: #element] should strictly satisfy: [collection add: #element]]
runScenario

Of course, this is a very early step of development, as I'll have to
implement an efficient algorithm to check, if element exists in the set
before adding it… And with mocks it is very likely I'll be driven to
implement it as a pluggable object. Probably, this can make Set a bit more
flexible, doesn't it? On the other hand, it can introduce unnecessary
complexity. Apparently, this is a good example to explore it further… While
I must admit technique of mock object is not a silver bullet, and must be
used carefully… just as any other tool.

So…

Conclusion 1:
Yes, mock objects is a specific tool for specific situations. But as for me,
I meet these specific situation almost as frequently, as other,
non-specific(?) circumstances.

Conclusion 2:
I think mocks should be used, first of all, to discover different aspects of
functionality and distribute it among various objects. (Documentation,
optimization, etc. goes later… if goes at all…)

When I start to work on a new feature, I first try to write an acceptance
test. But I never can implement it 'as is'. And I hate to leave it red or
mark it as ignored (in any way). So I try to transform it into something I
can implement easily and fast. And often this leads me to a test with mocks.
This make me to divide complex functionality into smaller blocks and factor
them out to collaborators. The trick is to make those objects to behave
(one-by-one and altogether)… But that's another (and big) topic.

--
Dennis Schetinin

First of all, I'm sorry for my English. (And I'll appreciate it very much if someone will point to mistakes of any kind in the following text.) I attempted to analyze and structure comments, questions and ideas presented in the thread and I think I found one thing that can be a starting point for the discussion (despite the fact the discussion already started… away back). I've got a strong feeling (I've got it several years ago and had a number of evidences, including several in this thread) that there are (at least) two very different (if not opposite) points of view on mock objects… and it seems to me, the root of this divergence is even deeper and more general — understanding of TDD. So, I think I have to explain my point of view. (Telling "my" here I claim to be an author of all errors, misinterpretations and nonsenses I'll present. But all the good, reasonable and interesting things were invented by other (clever) people.) So, why and how did I dare to claim TDD is not really popular among Smalltalkers? Because I still believe tests can drive (nearly) all stages of development: not just coding, but also design and even analysis (at least to clarify and formalize results of analysis). But I rarely (if ever) see any artifacts (tests of the kind). Perhaps I'm wrong, but most of the tests I saw were more "acceptance" tests, perhaps even written afterwards. Once again: very likely I'm wrong (and I'll be glad to be, actually). Why is it important for me to use TDD? Because, as a bad programmer, I need a help. I have a lot of things I must think about during development (won't enumerate, we all know them), so I want a tool that helps me not to think too much — at least, at the points where it is possible at all. (If I misinterpret the word "driving" here, please let me know.) Thus, if I write a line of code that is not directly(!) driven by a test, I don't TDD. If I make some up-front design I'm not actually test-driven. Even, if I simply go few steps designing top-down without tests at a very small single stage of development, TDD is broken. If you think it is too extreme and strict — ok, let's talk about "seamless TDD" vs., say, "partial TDD". (Yes, I know about steps of variable size during TDD etc. But I still think it's a bit another thing. Let's judge a bit later…) So, considering an example by Colin Putney: | set | set := Set new. set add: 3. self assert: (set includes: 3). vs | set | mock := MockArray new: 5 mock expect: (Message selector: #at:put: arguments: #(4 3)). set := Set withArray: mock. set add: 3. Isn't it obvious mocking array here is an overkill and the first test is better? For sure it is. But why? Because there's actually no development in the 1st test. Otherwise, what is it for: #add: or #includes:? Which feature this test makes me to add to the system-under-test (the Set)? I must select, I must think, thus I'm not driven by the test. Consider also (slightly) another issue: here we have a ready-to-use inner collection for the Set. But if I have no yet? Isn't it a common and very frequent situation during development: I have to implement a (sub)system that comprises several objects working together? (I think I don't have to provide examples here to prove.) Which one should I implement first? I don't know, I have to explore. I must analyze the system, i.e. divide it into subcomponents, find the ones independent and start by implementing them first. But what is this process, I've just described? It's a top-down up-front design. Is it test-driven? Obviously, no. Do I have an alternative? Yes, I can use mocks. With Mocketry I could write: SetTests >> testStoresElementsInInnerCollection [:collection | set := Set withArray: collection. [set add: #element] should strictly satisfy: [collection add: #element]] runScenario Of course, this is a very early step of development, as I'll have to implement an efficient algorithm to check, if element exists in the set before adding it… And with mocks it is very likely I'll be driven to implement it as a pluggable object. Probably, this can make Set a bit more flexible, doesn't it? On the other hand, it can introduce unnecessary complexity. Apparently, this is a good example to explore it further… While I must admit technique of mock object is not a silver bullet, and must be used carefully… just as any other tool. So… Conclusion 1: Yes, mock objects is a specific tool for specific situations. But as for me, I meet these specific situation almost as frequently, as other, non-specific(?) circumstances. Conclusion 2: I think mocks should be used, first of all, to discover different aspects of functionality and distribute it among various objects. (Documentation, optimization, etc. goes later… if goes at all…) When I start to work on a new feature, I first try to write an acceptance test. But I never can implement it 'as is'. And I hate to leave it red or mark it as ignored (in any way). So I try to transform it into something I can implement easily and fast. And often this leads me to a test with mocks. This make me to divide complex functionality into smaller blocks and factor them out to collaborators. The trick is to make those objects to behave (one-by-one and altogether)… But that's another (and big) topic. -- Dennis Schetinin
GK
Göran Krampe
Tue, May 31, 2011 9:21 PM

On 05/31/2011 04:59 PM, Frank Shearar wrote:

2011/5/31 Göran Krampegoran@krampe.se:

So refreshingly to see someone that also notes that testing using mocks
actually break encapsulation. Thank you, thank you...

Or to rephrase, "documenting the precise nature of the colloboration
between an object and its context documents that precise nature."

In Colin's example, Set's collaboration with Array is purely an
implementation detail and, as he notes, testing the implementation -
whether checking that Set stores the value in the array, or what
messages Set might send to Array - hurts you.

I fail to see how that makes mocks bad. Unless you're railing against
hype? In which case, I agree: there ain't no such thing as a free
lunch, there is no silver bullet, etc etc.

Well, yes, railing against hype. But also railing against writing tests
that work from "the inside" by using mocks. It is of course a matter of
taste, style and in the end weighing pros and cons. But I have seen too
much use of mocks that end up making the tests purely mirror the
implementation - so even the slightest internal modification would break
the test.

regards, Göran

On 05/31/2011 04:59 PM, Frank Shearar wrote: > 2011/5/31 Göran Krampe<goran@krampe.se>: >> So refreshingly to see someone that *also* notes that testing using mocks >> actually break encapsulation. Thank you, thank you... > > Or to rephrase, "documenting the precise nature of the colloboration > between an object and its context documents that precise nature." > > In Colin's example, Set's collaboration with Array is purely an > implementation detail and, as he notes, testing the implementation - > whether checking that Set stores the value in the array, or what > messages Set might send to Array - hurts you. > > I fail to see how that makes mocks bad. Unless you're railing against > hype? In which case, I agree: there ain't no such thing as a free > lunch, there is no silver bullet, etc etc. Well, yes, railing against hype. But also railing against writing tests that work from "the inside" by using mocks. It is of course a matter of taste, style and in the end weighing pros and cons. But I have seen too much use of mocks that end up making the tests purely mirror the implementation - so even the slightest internal modification would break the test. regards, Göran
FS
Frank Shearar
Wed, Jun 1, 2011 3:01 PM

2011/5/31 Göran Krampe goran@krampe.se:

On 05/31/2011 04:59 PM, Frank Shearar wrote:

2011/5/31 Göran Krampegoran@krampe.se:

So refreshingly to see someone that also notes that testing using mocks
actually break encapsulation. Thank you, thank you...

Or to rephrase, "documenting the precise nature of the colloboration
between an object and its context documents that precise nature."

In Colin's example, Set's collaboration with Array is purely an
implementation detail and, as he notes, testing the implementation -
whether checking that Set stores the value in the array, or what
messages Set might send to Array - hurts you.

I fail to see how that makes mocks bad. Unless you're railing against
hype? In which case, I agree: there ain't no such thing as a free
lunch, there is no silver bullet, etc etc.

Well, yes, railing against hype. But also railing against writing tests that
work from "the inside" by using mocks. It is of course a matter of taste,
style and in the end weighing pros and cons. But I have seen too much use of
mocks that end up making the tests purely mirror the implementation - so
even the slightest internal modification would break the test.

PragProg were kind enough to publish a very well-timed article:
http://pragprog.com/magazines/2011-06/practical-mock-advice

The punchline? "Generally, there is one overarching reason for why you
would use a mock. That reason is this: You care about an
implementation detail in your code. And when do you care? You care
when it drives behavior."

frank

2011/5/31 Göran Krampe <goran@krampe.se>: > On 05/31/2011 04:59 PM, Frank Shearar wrote: >> >> 2011/5/31 Göran Krampe<goran@krampe.se>: >>> >>> So refreshingly to see someone that *also* notes that testing using mocks >>> actually break encapsulation. Thank you, thank you... >> >> Or to rephrase, "documenting the precise nature of the colloboration >> between an object and its context documents that precise nature." >> >> In Colin's example, Set's collaboration with Array is purely an >> implementation detail and, as he notes, testing the implementation - >> whether checking that Set stores the value in the array, or what >> messages Set might send to Array - hurts you. >> >> I fail to see how that makes mocks bad. Unless you're railing against >> hype? In which case, I agree: there ain't no such thing as a free >> lunch, there is no silver bullet, etc etc. > > Well, yes, railing against hype. But also railing against writing tests that > work from "the inside" by using mocks. It is of course a matter of taste, > style and in the end weighing pros and cons. But I have seen too much use of > mocks that end up making the tests purely mirror the implementation - so > even the slightest internal modification would break the test. PragProg were kind enough to publish a very well-timed article: http://pragprog.com/magazines/2011-06/practical-mock-advice The punchline? "Generally, there is one overarching reason for why you would use a mock. That reason is this: You care about an implementation detail in your code. And when do you care? You care when it drives behavior." frank