[Zope-dev] Re: zope.sendmail Retry fixes and new state machine.

Marius Gedminas mgedmin at b4net.lt
Wed Mar 19 15:25:35 EDT 2008


On Mon, Mar 10, 2008 at 06:09:50PM +0100, Wichert Akkerman wrote:
[...the usual doctest vs unittest discussion ...]
> Lack of isolation is a very convincing argument to me.

For me as well.  Which is why I put my doctests into a traditional
tests/test_foo.py module like this:

    # ... imports etc ...

    def doctest_FooClass_somemethod():
        """Test FooClass.somemethod

            >>> foo = FooClass(ContextStub())

        When you frob the groddle, by default you get zlotniks

            >>> foo.frob(GroddleStub())
            '42 zlotniks'

        """

    def doctest_FooClass_somemethod_special_case():
        """Test FooClass.somemethod

            >>> foo = FooClass(ContextStub())

        There's a very special corner case that happens when the system runs
        out of zlotniks

            >>> foo.frob(GroddleStub(zlotniks_required=1000))
            Traceback (most recent call last):
              ...
            OutOfZlotniksError: 1000 zlotniks required, but only 50 are available

        """

    def test_suite():
        return doctest.DocTestSuite(setUp=setup.placelessSetUp,
                                    tearDown=setup.placelessTearDown,
                                    optionflags=doctest.NORMALIZE_WHITESPACE)


I find that this style helps me write much more readable tests without
sacrificing isolation.

Instead I sacrifice pdb debuggability, that I don't value much (I find
the pdb user interface horrible and prefer debugging with print
statements).

I also sacrifice test reusability -- you can't invoke one doctest from
the middle of another one.  (Footnotes don't count -- invoking a doctest
footnote more than once is just *evil*, because you never know which
invocation failed.).

I find that for me personally, the advantages of test readability
outweigh the disadvantages:

  * doctests without English test explaining each block of related
    assertions look naked.  Unittests without comments look normal.  As
    a result I feel the urge to explain my doctests when I never felt
    the urge to explain my unit tests.  Don't know why that is, but
    that's how I feel.

  * The ability to nicely format data for presentation is extremely
    helpful.  Compare

        >>> print_table([view.column_titles] + view.getTabularData())
        +------------------+------------------+--------------+
        | Month            | Widgets produced | Widgets sold |
        +------------------+------------------+--------------+
        | January          | 42               | 17           |
        | February         | 33               | 25           |
        +------------------+------------------+--------------+

    with

        self.assertEquals(view.getTabularData(),
                          [['January', 42, 17],
                           ['February', 33, 25]])

    Now compare the error messages when the test fails:

        Failed example:
            print_table([view.column_titles] + view.getTabularData())
        Differences (ndiff with -expected +actual):
              +------------------+------------------+--------------+
              | Month            | Widgets produced | Widgets sold |
              +------------------+------------------+--------------+
            - | January          | 42               | 17           |
                                    ^
            + | January          | 43               | 17           |
                                    ^
              | February         | 33               | 25           |
              +------------------+------------------+--------------+

    versus

        AssertionError: [['January', 42, 17], ['February', 33, 25]] != [['January', 43, 17], ['February', 33, 25]]

    I'm not saying that it's impossible to write a better table
    comparison function and use that instead of assertEquals.  I'm
    saying that with doctests it's easier and feels more natural.

> Perhaps more personal taste but I also find python unittests to be much
> more readable. You don't suffer from mixing lots of test setup/teardown
> being repeated through the file. As Tres mentioned this is especially
> true when testing corner cases.

My solution for repeating setup/teardown:

  * Put things common to all tests in the module (like placelessSetUp,
    setup.setUpAnnotations, registering your custom adapters needed for
    these tests) into global setUp/tearDown functions, and pass those to
    the DocTestSuite constructor.

  * Extract object creation into global reusable helper functions.  Then tests
    look like

        >>> context = createFrombulator()
        >>> view = FrombulatorEditView(context, TestRequest())

    and you don't have to care in every test that your Frombulator needs
    seventeen Wodgets in a nested tree of Grompets for correct
    functioning.  These helper functions are parametrizable, so that if
    I find out that I need a very custom wodget in a particular test, I
    can do

        >>> my_wodget = Wodget('foo', 'bar')
        >>> my_wodget.someSetUpOnlyUsedInThisTest(42, -17j)
        >>> context = createFrombulator(main_wodget=my_wodget)
        ...

> Being able to debug tests by stepping over them with pdb is incredibly
> useful. With doctests that doesn't work.

Right.  In this case doctests aren't the right choice for you.

> Being able to run a single test easily allows for quick testing and
> debugging. I can't tell the testrunner 'start running at line 52 but
> also include all the test setup magic from before, but skip the rest'.

This is one of the reasons why I prefer doctest modules to long
documentation-and-all-tests-rolled-into-one .txt files, despite best
efforts of Stephan Richter to convince me otherwise.  ;-)

> With
> unittests I can simple run zopectl test -s <module> -t <test function>.

Heh.  I've got a vim macro that (1) finds the dotted name of the module
I'm editing and (2) finds the nearest function definition above the
cursor and (3) constructs a command line to run

  bin/test -s subdir -m package.module -t testnamee

Yummy.  Sadly, vim is completely incapable of sanely executing a
long-running (which I define as "longer than 2 seconds") shell process
in the background, so I have to alt-tab to a terminal window and paste
the constructed command line that my vim macro puts into the clipboard.

> doctests hurt my productivity badly.

I'm happy to say that's not the case for me.

Now, *functional* tests do hurt my productivity sometimes.  They also
happen to be long-running slow doctests that compare multi-thousand-line
HTML pages to ad-hoc patterns built with the doctest.ELLIPSIS feature.
Pain.  A recent decision to bite the bullet and introduce a dependency
on lxml so that we could compare just the interesting snippets of HTML,
extracted with xpath queries, helps somewhat.  The abysmal rate of ~6
zope.testbrowser requests per second on a modern 1.8 GHz Core 2 Duo
machine doesn't, but that's a different topic.

Marius Gedminas
-- 
I code in vi because I don't want to learn another OS. :)
                -- Robert Love
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: Digital signature
Url : http://mail.zope.org/pipermail/zope-dev/attachments/20080319/1b350096/attachment.bin


More information about the Zope-Dev mailing list