test isolation: default ignores & repository acquisition

Vincent Ladeuil v.ladeuil+lp at free.fr
Tue Sep 22 13:51:12 BST 2009


A more complete answer requires code, which in the end, is the
only thing that matter. But the merge tests are not an easy
target, so I just comment below :-/

Roughly, the merge tests need love in at least 3 areas:
- more parametrization,
- make the merge *code* more testable,
- more support from MemoryTransport.

>>>>> "jam" == John Arbash Meinel <john at arbash-meinel.com> writes:

<snip/>

    jam> So I disagree with Vincent, which was my original
    jam> example. It isn't a big deal when there are 2 tests. But
    jam> when you have 2 pages of tests, tracking back to find
    jam> which test case this is part of is difficult.  Tracking
    jam> up 3 lines to see what setup is called is fairly
    jam> immediately obvious.

1) Shorter tests address your point, by making tests shorter you
can see more in the same page. That means: put more code in
setUp() and create higher level helpers.

2) I don't buy the argument that tests have to be on the same
page that their setup. It's a bit like saying that inheritance
can be used only if all classes fit in the same page.

    >> 
    >> Can you show me examples where your approach give benefits, and
    >> which ones[1] ?

    jam> So my direct set of test cases where this was an issue was:
    jam> bzrlib.tests.test_merge.py
    jam>   TestMergerEntriesLCA
    jam> and
    jam>   TestMergerEntriesLCAOnDisk

    jam> Now, all of those test cases have rather complex
    jam> setup. They are rather hard tests to decipher.

Right, that make them good candidates for refactoring. Complex
setup does not mean the test itself *has* to be hard to read (nor
write), it mostly means we lack the right setup tools.

    jam> They were just about as hard to create, and I tried to
    jam> do a decent job of explaining them in comments. (Not to
    jam> mention that the final "assertEqual" test is way to
    jam> complex, and the api is full of far too many
    jam> tuples... :)

All I can say at that point is that you recognize that the actual
tests are unclear. I agree, we should do better :)

    jam> I'm pretty sure that I could have used slightly more inheritance in some
    jam> of the tests. For example the:
    jam>   test_conflict_without_lca
    jam>   test_other_deletes_lca_renames
    jam>   test_executable_changes
    jam>   test_create_symlink
    jam>  ...

    jam> All use a similar shaped graph. And a couple of times the specific
    jam> difference is probably in just the final commit.

"just in the final commit" sounds to me as:
- common code in setUp()
- final commit in the test

    jam> Anyway, it was a pain for me to have to care whether I
    jam> had a disk backing to work with or not.

    jam> And figure out what tests could be done "cheaply" and
    jam> what tests could not (for comparison, first runs 23
    jam> cases in 2.4s = 104ms/test, OnDisk is 19 tests in 6.0s =
    jam> 315ms/test)

But only because you tried (and rightly so !) to separate tests
that could run without disk support from those that required disk
support.

So here I'm tempted to say that you may not have to care if:
1) we could use MemoryTransport in more cases,
2) the merge code was more testable.

    jam> Also note that there is a TestMergerInMemory() which has a similar split
    jam> out for things that I could test without creating a WT.

    jam> And note that TestMergerEntriesLCAOnDisk can't inherit from the same
    jam> hierarchy as TestMergerEntriesLCA because it needs the WithTransport
    jam> base class. (Arguably the whole thing could be pulled out to the side as
    jam> mixins that don't get their final base until the very end.)

Again, you may not need all of that if you can come closer to the code to test.

    jam> Now, potentially we could have turned all of these tests
    jam> into individual classes, and moved the setup
    jam> appropriately.

You generally find some commonality to group test by classes
(even when doing "test after" instead of "test before").

    jam> Or made a *much* bigger setUp that would have to deal
    jam> with 20 different permutations and then had the final
    jam> assertion test just test one aspect of that large set
    jam> up.

Different permutations is what parametrization is for.

    jam> I don't have great answers here. And if you can show me
    jam> a way to make those tests easier to
    jam> read/maintain/develop I'd be very happy to see it.

I certainly want to help here and I'll see what I can do.

    jam> I certainly created the "builder.build_snapshot()"
    jam> functionality explicitly to help me do the work, because
    jam> managing 'tree' objects and knowing what revision I was
    jam> on during 'tree.merge_from_branch' was killing me.

    jam> As is, there is a lot of voodoo there.

My gut feeling is that this is mainly due to the fact the code is
not testable enough.

    jam> Though I'm also pleased that I actually did do TDD as
    jam> much as possible there, and didn't fix things I knew
    jam> were wrong until I could get a test to exercise that
    jam> code.

Yup, I saw that on several occasions.

     Vincent




More information about the bazaar mailing list