[Product-Developers] Re: Mock testing

David Bain david.bain at alteroo.com
Wed Jul 2 20:54:52 UTC 2008


Wow

* easy to understand
* quick to write
* fast to run

Contrary to what I may have indicated in the past, I have actually written
products with tests, while the tests were valuable, they were generally:

* challenging to read
* hard to write
* slow to run

As a result, I have tended to write fewer tests than I should on projects.

If this thing works as advertised, I may begin to "sup" at the testing
"table" more often.

On Wed, Jul 2, 2008 at 3:41 PM, Martin Aspeli <optilude at gmx.net> wrote:

> Hi Maurits,
>
>  I have not tried any code (also not from Dexterity), only read your
>> article.  Some things are not clear to me.
>>
>> - At the end of the first mock example you say: "We must remember to put
>>  the test case into replay mode, using self.replay()."  Why?  What does
>>  that do?
>>
>
> Before you put the mock into replay mode, it's in "record" mode. All
> operations you do on it are essentially expectations. For example:
>
>  mymock = self.mocker.mock()
>  self.expect(mymock.foo()).result(True)
>
> Now, if I do
>
>  mymock.foo()
>
> without putthing the mock in replay mode, it's basically just recording an
> expectation that foo will be called. In fact, the above could have been
> rewritten as:
>
>  mymock = self.mocker.mock()
>  mymock.foo()
>  self.mocker.result(True)
>
> though I prefer the self.expect() chaining syntax as it's more compact.
>
> Only when the mock is put in replay mode will it actually exhibit the
> behaviour and check that it's being called in the expected way. You do that
> with
>
>  self.replay()
>
> This is how most mock libraries work. You start off in record mode, doing
> operations on the object that "record" expectations about how it should be
> called. You then put it into "replay" mode and call the code under test
> (initialised to use the mock, obviously). The mock library then checks that
> recorded operations actually happen (in the way that they were recorded) and
> throws assertion errors if they're not. Finally, you do a verify + restore
> (implicit in the unit test) that ensures no steps were missed and unpatches
> anything patched for the test.
>
>  - In the third mock example you say that we "set the expectation that
>>  lookup_schema() should be called on it once", stressing the word
>>  'once'.  Which part of the test code does that?  From the paragraphs
>>  after that I *guess* that whenever the code is this:
>>
>>    self.expect(...).result(...)
>>
>>  it implicitly means this:
>>
>>    self.expect(...).result(...).count(1, 1)
>>
> >
>
>>  Is that a correct guess?
>>
>
> Yes.
>
>   If so, I think you should mention that
>>  explicitly, perhaps already in the first mock example.
>>
>
> In record mode, the assumption is that if you record an action once, you
> meant for it to happen once. Let's say I have a mock and I expect it to be
> called twice, returning one thing the first time and something else the
> second time:
>
>  magic_eight_ball = self.mocker.mock()
>  self.expect(magic_eight_ball()).result("No")
>  self.expect(magic_eight_ball()).result("Yes")
>
>   BTW, if that is correct then I would find "count(0, None)" a more
>>  logical default, so by default no restriction on the number of times
>>  a mocked method is called.
>>
>
> Most mock libraries don't work like this. They fail when a method is called
> too many times (and will tell you so). I think it's (marginally) better to
> be explicit if you don't care how many times something's called. It's likely
> that if you expected something to be called, and it's called 0 times, then
> it's an error, and if it's called dozens of times, it's also an error.
>
>  And a question: is there next to mock_utility also something like
>> mock_tool, so you could you this to mock for example the
>> portal_catalog?  I guess something along these lines might work:
>>
>>  from whereever import CatalogTool
>>  catalog_mock = self.mocker.proxy(CatalogTool())
>>  context_mock = self.mocker.mock()
>>
>>  self.expect(context_mock.portal_catalog).result(catalog_mock).count(0,None)
>>
>
> No. There could be, but bear in mind that mock tests are not running in a
> PloneTestCase (which is why they're so quick) - at least not normally (no
> reason they couldn't be, but then you're really doing an integration test).
> As such, if your code calls getToolByName(), you'd probably do a replace()
> on this.
>
> A generic mock_tool() could look like this, though:
>
>  from mocker import ANY
>
>  class MockTestCase(...):
>     ...
>     getToolByName_mock = None
>
>     def mock_tool(self, tool_mock, name, expected_context=ANY,
>                    min=1, max=None):
>         mock = self.getToolByName_mock
>         if mock is None:
>             self.getToolByName_mock = mock = \
>             self.mocker.replace('Products.CMFCore.utils.getToolByName')
>         self.expect(mock(expected_context,
>                             name)).result(tool_mock).count(min, max)
>
> Untested and possibly refactorable of course.
>
> Martin
>
> --
> Author of `Professional Plone Development`, a book for developers who
> want to work with Plone. See http://martinaspeli.net/plone-book
>
>
> _______________________________________________
> Product-Developers mailing list
> Product-Developers at lists.plone.org
> http://lists.plone.org/mailman/listinfo/product-developers
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.plone.org/pipermail/plone-product-developers/attachments/20080702/df1f7088/attachment-0001.html>


More information about the Product-Developers mailing list