launchpad-dev team mailing list archive
-
launchpad-dev team
-
Mailing list archive
-
Message #04459
Lighter tests with FakeLibrarian
-
To:
Launchpad Community Development Team <launchpad-dev@xxxxxxxxxxxxxxxxxxx>
-
From:
Jeroen Vermeulen <jtv@xxxxxxxxxxxxx>
-
Date:
Tue, 24 Aug 2010 23:18:35 +0700
-
User-agent:
Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.11) Gecko/20100713 Thunderbird/3.0.6
Hi all,
This is to let you know about something I (mostly) cooked up in Prague.
The FakeLibrarian is a lightweight substitute for the Librarian that
you can use in single-process tests.
== Why use this? ==
Weight. The test framework no longer needs to set up the Librarian at
the beginning of a test run (or verify that it is running). Your test
no longer speaks http to a daemon, your data is no longer going to the
filesystem, and so on.
Simplicity. Things do go wrong sometimes when you run the Librarian
persistently, and getting things back up on their feet can be a
nuisance. FakeLibrarian does it all in-memory.
Helpfulness. Ever have a test break because you forgot to commit after
adding a file to the Librarian? The real librarian says "I couldn't
find something." The fake one says "I have your file but you forgot to
commit."
== How does it work? ==
You set up a fake librarian in your test setup, and uninstall it during
teardown. Your test proceeds as if you had the normal Librarian
running: getUtility(ILibrarianClient) etc. still work, but return the
fake librarian instead of the real one.
The LibraryFileAlias and LibraryFileContent objects are still really in
the database as before, so you can still join and query them.
== How do you use it? ==
Your old test may have looked something like this:
class TestKitchenAppliance(TestCaseWithFactory):
layer = LaunchpadLayer
def setUp(self):
super(TestKitchenAppliance, self).setUp()
other_setup()
def tearDown(self):
other_teardown()
super(TestKitchenAppliance, self).tearDown()
def test_prepare_recipe(self):
# The prepare_recipe function takes a LibraryFileAlias.
recipe = "Dice onion with extreme prejudice."
cookbook_alias = getUtility(ILibrarianClient).addFile(
'recipe.txt', len(recipe), StringIO(recipe), 'text/plain')
# Needed to keep Librarian happy.
transaction.commit()
result = prepare_recipe(cookbook_alias)
self.assertEqual(True, result)
Here's how you would convert that test to the fake librarian:
* Import:
from lp.testing.fakelibrarian import FakeLibrarian
* Optionally downgrade the layer to something lighter, e.g.:
layer = DatabaseFunctionalLayer
* In setUp, create a FakeLibrarian and substitute it for the real one:
self.fake_librarian = FakeLibrarian()
self.fake_librarian.installAsLibrarian()
* In tearDown, remove the FakeLibrarian:
self.fake_librarian.uninstall()
* Optionally replace transaction.commit() with:
self.fake_librarian.pretendCommit()
* Don't forget to remove the "import transaction" at the top. :-)
The single FakeLibrarian object implements all Librarian-related
interfaces and utilities you're likely to need.
For examples of the FakeLibrarian in action, see:
- lib/lp/testing/tests/test_fakelibrarian.py
- lib/lp/translations/utilities/tests/test_file_importer.py
In test_fakelibrarian.py you'll see the same tests applied to both the
real librarian and the fake one. That's where we test how lifelike the
FakeLibrarian is.
== When doesn't it work? ==
Of course "lightweight" comes at a price. There are limitations:
In-process. You can't use the FakeLibrarian to hand files from one
process to another, or set up files in your test and then access them
from a script you run.
Non-http. The FakeLibrarian will generate the same URLs for your files
as the real Librarian does, but you can't access those URLs. There's no
remoteAddFile either.
Incomplete. There are some more intimate Librarian APIs that the
FakeLibrarian doesn't support. I don't think they're used much though.
And of course you should not use the FakeLibrarian for testing behaviour
of the real Librarian. But you figured that out already. :)
== So does it? Work, I mean? ==
It does for me. I tried converting one test case and got roughly a 20%
speedup, both for setup and for running the tests. That's compared to a
real Librarian and memcached that are already running before the test
starts.
Jeroen
Follow ups