Friday, January 10, 2014

From nose to testr: Output Capture

The thing that drove me most crazy when I was trying to write nose-testresources (a plugin for nose that would put ResourcedTestCases inside OptimisedTestSuites) was that I lost nose's default behaviour of capturing log output and stdout. I put a lot of work into logging information that would help me troubleshoot failures, and was flailing without it.

I talked to Robert Collins, the primary maintainer of this chain of tools, and he suggested I use testtools.TestCase, which supports addFixture() and python-fixtures, which includes FakeLogger. Now, this transition turned out to be a little bit sticky - both testtools.TestCase and testresources.ResouredTestCase inherit from unittest.TestCase, so dual inheritance should have worked, but I was running into this strange problem that resources were never being cleaned up. It turned out that testtools.TestCase calls setUp() but doesn't call tearDown() - it uses addCleanup() for that purpose. As part of the debugging process for this issue, I axed the multiple inheritance an just created a ResourcedTestCase that follows testtools.TestCase norms as follows:


from testresources import setUpResources, tearDownResources, _get_result
import testtools
import fixtures

class ResourcedTestCase(testtools.TestCase):
    """Inherit from testtools.TestCase instead of unittest.TestCase in order
    to have self.useFixture()."""

    def setUp(self):
        # capture output
        FORMAT = '%(asctime)s [%(levelname)s] %(name)s %(lineno)d: %(message)s'
        self.useFixture(fixtures.FakeLogger(format=FORMAT))
        # capture stdout
        stdout = self.useFixture(fixtures.StringStream('stdout')).stream
        self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
        # capture stderr
        stderr = self.useFixture(fixtures.StringStream('stderr')).stream
        self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
        super(ResourcedTestCase, self).setUp()
        setUpResources(self, self.resources, _get_result)
        self.addCleanup(tearDownResources, self, self.resources, _get_result)


With this new base class, my tests now capture logging, stdout, and stderr under both testr and nose, with and without nose-testresources.

No comments:

Post a Comment