diff --git a/MANUAL b/MANUAL index f421faf..0b2f6da 100644 --- a/MANUAL +++ b/MANUAL @@ -116,6 +116,16 @@ instead. ``skipTest`` was previously known as ``skip`` but as Python 2.7 adds ``skipTest`` support, the ``skip`` name is now deprecated (but no warning is emitted yet - some time in the future we may do so). +TestCase.useFixture +~~~~~~~~~~~~~~~~~~~ + +``useFixture(fixture)`` calls setUp on the fixture, schedules a cleanup to +clean it up, and schedules a cleanup to attach all details held by the +fixture to the details dict of the test case. The fixture object should meet +the ``fixtures.Fixture`` protocol (version 0.3.4 or newer). This is useful +for moving code out of setUp and tearDown methods and into composable side +classes. + New assertion methods ~~~~~~~~~~~~~~~~~~~~~ diff --git a/NEWS b/NEWS index 6136543..50de804 100644 --- a/NEWS +++ b/NEWS @@ -24,6 +24,9 @@ Improvements * addUnexpectedSuccess is translated to addFailure for test results that don't know about addUnexpectedSuccess. (Jonathan Lange, #654474) +* ``testools.TestCase.useFixture`` has been added to glue with fixtures nicely. + (Robert Collins) + 0.9.7 ~~~~~ diff --git a/README b/README index 8afa13c..83120f0 100644 --- a/README +++ b/README @@ -19,14 +19,21 @@ is copyright Steve Purcell and the Python Software Foundation, it is distributed under the same license as Python, see LICENSE for details. -Dependencies ------------- +Required Dependencies +--------------------- * Python 2.4+ or 3.0+ +Optional Dependencies +--------------------- + If you would like to use our undocumented, unsupported Twisted support, then you will need Twisted. +If you want to use ``fixtures`` then you can either install fixtures (e.g. from +https://launchpad.net/python-fixtures or http://pypi.python.org/pypi/fixtures) +or alternatively just make sure your fixture objects obey the same protocol. + Bug reports and patches ----------------------- diff --git a/testtools/testcase.py b/testtools/testcase.py index 71a236b..fd6b277 100644 --- a/testtools/testcase.py +++ b/testtools/testcase.py @@ -538,6 +538,35 @@ class TestCase(unittest.TestCase): """ return self._get_test_method()() + def useFixture(self, fixture): + """Use fixture in a test case. + + The fixture will be setUp, and self.addCleanup(fixture.cleanUp) called. + + :param fixture: The fixture to use. + :return: The fixture, after setting it up and scheduling a cleanup for + it. + """ + fixture.setUp() + self.addCleanup(fixture.cleanUp) + self.addCleanup(self._gather_details, fixture.getDetails) + return fixture + + def _gather_details(self, getDetails): + """Merge the details from getDetails() into self.getDetails().""" + details = getDetails() + my_details = self.getDetails() + for name, content_object in details.items(): + new_name = name + disambiguator = itertools.count(1) + while new_name in my_details: + new_name = '%s-%d' % (name, advance_iterator(disambiguator)) + name = new_name + content_bytes = list(content_object.iter_bytes()) + content_callback = lambda:content_bytes + self.addDetail(name, + content.Content(content_object.content_type, content_callback)) + def setUp(self): unittest.TestCase.setUp(self) self.__setup_called = True diff --git a/testtools/tests/test_testtools.py b/testtools/tests/test_testtools.py index 9fac679..8dd59f3 100644 --- a/testtools/tests/test_testtools.py +++ b/testtools/tests/test_testtools.py @@ -6,6 +6,9 @@ from pprint import pformat import sys import unittest +import fixtures +from fixtures.tests.helpers import LoggingFixture + from testtools import ( ErrorHolder, MultipleExceptions, @@ -13,6 +16,7 @@ from testtools import ( TestCase, clone_test_with_new_id, content, + content_type, skip, skipIf, skipUnless, @@ -1122,6 +1126,59 @@ class TestPatchSupport(TestCase): self.assertIs(marker, value) +class TestFixtureSupport(TestCase): + + def test_useFixture(self): + fixture = LoggingFixture() + class SimpleTest(TestCase): + def test_foo(self): + self.useFixture(fixture) + result = unittest.TestResult() + SimpleTest('test_foo').run(result) + self.assertTrue(result.wasSuccessful()) + self.assertEqual(['setUp', 'cleanUp'], fixture.calls) + + def test_useFixture_cleanups_raise_caught(self): + calls = [] + def raiser(ignored): + calls.append('called') + raise Exception('foo') + fixture = fixtures.FunctionFixture(lambda:None, raiser) + class SimpleTest(TestCase): + def test_foo(self): + self.useFixture(fixture) + result = unittest.TestResult() + SimpleTest('test_foo').run(result) + self.assertFalse(result.wasSuccessful()) + self.assertEqual(['called'], calls) + + def test_useFixture_details_captured(self): + class DetailsFixture(fixtures.Fixture): + def setUp(self): + fixtures.Fixture.setUp(self) + self.addCleanup(delattr, self, 'content') + self.content = ['content available until cleanUp'] + self.addDetail('content', + content.Content(content_type.UTF8_TEXT, self.get_content)) + def get_content(self): + return self.content + fixture = DetailsFixture() + class SimpleTest(TestCase): + def test_foo(self): + self.useFixture(fixture) + # Add a colliding detail (both should show up) + self.addDetail('content', + content.Content(content_type.UTF8_TEXT, lambda:['foo'])) + result = ExtendedTestResult() + SimpleTest('test_foo').run(result) + self.assertEqual('addSuccess', result._events[-2][0]) + details = result._events[-2][2] + self.assertEqual(['content', 'content-1'], sorted(details.keys())) + self.assertEqual('foo', ''.join(details['content'].iter_text())) + self.assertEqual('content available until cleanUp', + ''.join(details['content-1'].iter_text())) + + def test_suite(): from unittest import TestLoader return TestLoader().loadTestsFromName(__name__)