[rfc] add a TestFactory class or concept

Aaron Bentley aaron at aaronbentley.com
Thu Aug 13 23:40:36 BST 2009


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Martin Pool wrote:
>> Martin Pool wrote:
>>> Lots of tests are run on some specific values; for instance they make
>>> a branch and then do some operations on it.
>> We need specific values in order to make assertions about them, and to
>> provide legibility.  Could you please explain what about this is bad?
> 
> I will try.
> 
> It's not quite true that you need specific values in order to make
> assertions about them: a very common case is that we commit a revision
> and then check that its id is retrieved later, without ever hardcoding
> the revision id.

I think this is a good example, because it gets at a lot of the issues.

I don't think we're making assertions about specific values in that
case.  In an example where we were making assertions about specific
values, we would specify a revision-id, and then later test that the
literal revision-id had been used.

So if you'd like to argue instead that we rarely need to make assertions
about specific values, I'd certainly agree it's not a need.

We can test almost everything by allowing the the revision-id to be
auto-generated and storing it in a variable.  (We can't test that
specifying a revision-id works, of course.)  But I find that the
indirection through that variable name makes the test more abstract,
less concrete, and harder to follow.

> Another case is that all tests run in a temporary
> directory and they commonly make assertions about the contents of the
> filesystem even though the precise path they will have is determined
> at run time.

This is some kind of middle ground between specified and unspecified.
Semi-specified?  We create a scenario with values that are specified
relative to the cwd.  Then we test the outcome with the values we had
specified earlier, relative to the cwd.  I agree that we do not specify
full paths, but the tests would fail if our specified relative paths
were ignored.

> Problems with hardcoding particular values include: the code may be
> wrong for other particular values; it may be longer than it needs to
> be; it's hard to update it to test other particular values or for
> other changes; it tends to be repetitive/redundant.
> 
> One case where it's harder to update: the info tests repeat the
> representation of bzr's default repository format many times, but what
> they actually want to assert in most cases is that the output contains
> an appropriate format name.
> 
> I'm suggesting that we don't always get this code right in either direction.
> 
>>> I was talking to jml the other day and he said that Launchpad have a
>>> cleaner separation of all of this setup into a TestFactory class
>> Cleaner or not, I find bzr's approach much easier to use.
> 
> .. so I'd like to work out what's wrong with what Launchpad does too,
> so as not to repeat it, if there's anything else you can think of.

Separating the test factory class from the test case means more typing
for two reasons:

1. The name of the factory object must be specified.

When writing tests for Launchpad, I usually subclass the
TestCaseWithFactory, which has a member named "factory".  So instead of
typing "self.makePerson", I must type "self.factory.makePerson".

2. Cleanup must be manually specified.

Many test fixtures need to be cleaned up-- they need locks released,
etc.  A method of TestCaseWithTransport can arrange for cleanup using
addCleanup, but a method of a test factory like Launchpad's cannot use
this facility.  This means that clients must manually specify cleanup.

We could alter the design so that the factory was initialized with a
cleanup facility.  We could alter the design so that the factory also
provided cleanup, and TestCase.tearDown would call it.  (Though I'd
worry about doing things out of order then.)

>>> (iirc) - if you want a Person to test, you would always get that from
>>> the factory rather than putting arbitrary example data into the class.
>> I don't understand.  There are plenty of examples where we create Person
>> objects with specific email addresses and names.  Look at this method
>> signature:
>>
>>    def makePersonNoCommit(
>>        self, email=None, name=None, password=None,
>>        email_address_status=None, hide_email_addresses=False,
>>        displayname=None, time_zone=None, latitude=None, longitude=None):
>>
>> These are all the things we've felt a need to specify in one test case
>> or another.
>>
>> It's true that we don't have to specify anything at all to makePerson.
>> If you were arguing that we should improve our existing helpers, I could
>> understand that, but I don't see how it relates to a TestFactory concept.
> 
> So, what is the different between our make* methods and a 'TestFactory
> concept'?  I'm not sure.  To me they're similar things; I used the
> name to suggest giving it more prominence or standardization.

That didn't seem to be your thrust at all.  I'm sorry if I
misunderstood, but I was reacting to the idea of adding a TestFactory
object, like Launchpad's, to bzr.  If that's not what you meant, then my
response must seem quite incoherent.

> Generally speaking we add make_* methods on various test classes,
> rather than centralizing them.  Many of them are things that could be
> more broadly reused.

This wasn't a problem I had noticed.  We have make_branch and
make_branch_and_tree, and I thought you were proposing making them
harder to use.  If instead, you're saying "make_branch is one honking
great idea, let's do more of them", then I'm 100% in favour.

>> I've never used BranchBuilder, for
>> example, and hardly ever used MemoryTree.
> 
> I think this is an interesting loose thread to pull upon - just
> exactly the kind of thing I was thinking about.  We have classes that
> are meant to make tests faster to run and easier to write, but they're
> not used consistently and you hardly use them at all.  So there's
> clearly something wrong here.  Possibly we should make those classes
> better, possibly we should give up on the whole idea.

In general, I don't see the advantages in speed being worth the cost in
simplicity.

MemoryTree is not close enough to a real working tree for me to be
comfortable with it.  I don't trust it to do what I want, and I don't
trust it to be a good enough emulation of a working tree.  I'd rather
use a PreviewTree, since I know and trust the TreeTransform interface.

BranchBuilder-- well, no compelling case has been given to me for using
it.  It wouldn't be appropriate for me to comment further until I
understand what it's meant for.

Aaron
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkqElmAACgkQ0F+nu1YWqI1pzACfW0iP8FhZYWPwm3AYwuw+Qs/t
/UIAnjqL9mSv3IrjK2/Ru7Lglj6EvwA+
=dvby
-----END PGP SIGNATURE-----




More information about the bazaar mailing list