diff options
Diffstat (limited to 'lib/testtools/testtools/tests/test_testtools.py')
-rw-r--r-- | lib/testtools/testtools/tests/test_testtools.py | 364 |
1 files changed, 347 insertions, 17 deletions
diff --git a/lib/testtools/testtools/tests/test_testtools.py b/lib/testtools/testtools/tests/test_testtools.py index af1fd794c3..9edc5a5176 100644 --- a/lib/testtools/testtools/tests/test_testtools.py +++ b/lib/testtools/testtools/tests/test_testtools.py @@ -1,11 +1,14 @@ -# Copyright (c) 2008 Jonathan M. Lange. See LICENSE for details. +# Copyright (c) 2008-2010 Jonathan M. Lange. See LICENSE for details. """Tests for extensions to the base test library.""" +from pprint import pformat import sys import unittest from testtools import ( + ErrorHolder, + PlaceHolder, TestCase, clone_test_with_new_id, content, @@ -26,6 +29,167 @@ from testtools.tests.helpers import ( ) +class TestPlaceHolder(TestCase): + + def makePlaceHolder(self, test_id="foo", short_description=None): + return PlaceHolder(test_id, short_description) + + def test_id_comes_from_constructor(self): + # The id() of a PlaceHolder is whatever you pass into the constructor. + test = PlaceHolder("test id") + self.assertEqual("test id", test.id()) + + def test_shortDescription_is_id(self): + # The shortDescription() of a PlaceHolder is the id, by default. + test = PlaceHolder("test id") + self.assertEqual(test.id(), test.shortDescription()) + + def test_shortDescription_specified(self): + # If a shortDescription is provided to the constructor, then + # shortDescription() returns that instead. + test = PlaceHolder("test id", "description") + self.assertEqual("description", test.shortDescription()) + + def test_repr_just_id(self): + # repr(placeholder) shows you how the object was constructed. + test = PlaceHolder("test id") + self.assertEqual( + "<testtools.testcase.PlaceHolder(%s)>" % repr(test.id()), + repr(test)) + + def test_repr_with_description(self): + # repr(placeholder) shows you how the object was constructed. + test = PlaceHolder("test id", "description") + self.assertEqual( + "<testtools.testcase.PlaceHolder(%r, %r)>" % ( + test.id(), test.shortDescription()), + repr(test)) + + def test_counts_as_one_test(self): + # A placeholder test counts as one test. + test = self.makePlaceHolder() + self.assertEqual(1, test.countTestCases()) + + def test_str_is_id(self): + # str(placeholder) is always the id(). We are not barbarians. + test = self.makePlaceHolder() + self.assertEqual(test.id(), str(test)) + + def test_runs_as_success(self): + # When run, a PlaceHolder test records a success. + test = self.makePlaceHolder() + log = [] + test.run(LoggingResult(log)) + self.assertEqual( + [('startTest', test), ('addSuccess', test), ('stopTest', test)], + log) + + def test_call_is_run(self): + # A PlaceHolder can be called, in which case it behaves like run. + test = self.makePlaceHolder() + run_log = [] + test.run(LoggingResult(run_log)) + call_log = [] + test(LoggingResult(call_log)) + self.assertEqual(run_log, call_log) + + def test_runs_without_result(self): + # A PlaceHolder can be run without a result, in which case there's no + # way to actually get at the result. + self.makePlaceHolder().run() + + def test_debug(self): + # A PlaceHolder can be debugged. + self.makePlaceHolder().debug() + + +class TestErrorHolder(TestCase): + + def makeException(self): + try: + raise RuntimeError("danger danger") + except: + return sys.exc_info() + + def makePlaceHolder(self, test_id="foo", error=None, + short_description=None): + if error is None: + error = self.makeException() + return ErrorHolder(test_id, error, short_description) + + def test_id_comes_from_constructor(self): + # The id() of a PlaceHolder is whatever you pass into the constructor. + test = ErrorHolder("test id", self.makeException()) + self.assertEqual("test id", test.id()) + + def test_shortDescription_is_id(self): + # The shortDescription() of a PlaceHolder is the id, by default. + test = ErrorHolder("test id", self.makeException()) + self.assertEqual(test.id(), test.shortDescription()) + + def test_shortDescription_specified(self): + # If a shortDescription is provided to the constructor, then + # shortDescription() returns that instead. + test = ErrorHolder("test id", self.makeException(), "description") + self.assertEqual("description", test.shortDescription()) + + def test_repr_just_id(self): + # repr(placeholder) shows you how the object was constructed. + error = self.makeException() + test = ErrorHolder("test id", error) + self.assertEqual( + "<testtools.testcase.ErrorHolder(%r, %r)>" % (test.id(), error), + repr(test)) + + def test_repr_with_description(self): + # repr(placeholder) shows you how the object was constructed. + error = self.makeException() + test = ErrorHolder("test id", error, "description") + self.assertEqual( + "<testtools.testcase.ErrorHolder(%r, %r, %r)>" % ( + test.id(), error, test.shortDescription()), + repr(test)) + + def test_counts_as_one_test(self): + # A placeholder test counts as one test. + test = self.makePlaceHolder() + self.assertEqual(1, test.countTestCases()) + + def test_str_is_id(self): + # str(placeholder) is always the id(). We are not barbarians. + test = self.makePlaceHolder() + self.assertEqual(test.id(), str(test)) + + def test_runs_as_error(self): + # When run, a PlaceHolder test records a success. + error = self.makeException() + test = self.makePlaceHolder(error=error) + log = [] + test.run(LoggingResult(log)) + self.assertEqual( + [('startTest', test), + ('addError', test, error), + ('stopTest', test)], log) + + def test_call_is_run(self): + # A PlaceHolder can be called, in which case it behaves like run. + test = self.makePlaceHolder() + run_log = [] + test.run(LoggingResult(run_log)) + call_log = [] + test(LoggingResult(call_log)) + self.assertEqual(run_log, call_log) + + def test_runs_without_result(self): + # A PlaceHolder can be run without a result, in which case there's no + # way to actually get at the result. + self.makePlaceHolder().run() + + def test_debug(self): + # A PlaceHolder can be debugged. + self.makePlaceHolder().debug() + + class TestEquality(TestCase): """Test `TestCase`'s equality implementation.""" @@ -47,16 +211,16 @@ class TestAssertions(TestCase): def test_formatTypes_single(self): # Given a single class, _formatTypes returns the name. - class Foo: + class Foo(object): pass self.assertEqual('Foo', self._formatTypes(Foo)) def test_formatTypes_multiple(self): # Given multiple types, _formatTypes returns the names joined by # commas. - class Foo: + class Foo(object): pass - class Bar: + class Bar(object): pass self.assertEqual('Foo, Bar', self._formatTypes([Foo, Bar])) @@ -164,7 +328,7 @@ class TestAssertions(TestCase): def test_assertIsInstance(self): # assertIsInstance asserts that an object is an instance of a class. - class Foo: + class Foo(object): """Simple class for testing assertIsInstance.""" foo = Foo() @@ -174,10 +338,10 @@ class TestAssertions(TestCase): # assertIsInstance asserts that an object is an instance of one of a # group of classes. - class Foo: + class Foo(object): """Simple class for testing assertIsInstance.""" - class Bar: + class Bar(object): """Another simple class for testing assertIsInstance.""" foo = Foo() @@ -188,7 +352,7 @@ class TestAssertions(TestCase): # assertIsInstance(obj, klass) fails the test when obj is not an # instance of klass. - class Foo: + class Foo(object): """Simple class for testing assertIsInstance.""" self.assertFails( @@ -199,10 +363,10 @@ class TestAssertions(TestCase): # assertIsInstance(obj, (klass1, klass2)) fails the test when obj is # not an instance of klass1 or klass2. - class Foo: + class Foo(object): """Simple class for testing assertIsInstance.""" - class Bar: + class Bar(object): """Another simple class for testing assertIsInstance.""" self.assertFails( @@ -251,20 +415,22 @@ class TestAssertions(TestCase): 'None is None: foo bar', self.assertIsNot, None, None, "foo bar") def test_assertThat_matches_clean(self): - class Matcher: + class Matcher(object): def match(self, foo): return None self.assertThat("foo", Matcher()) def test_assertThat_mismatch_raises_description(self): calls = [] - class Mismatch: + class Mismatch(object): def __init__(self, thing): self.thing = thing def describe(self): calls.append(('describe_diff', self.thing)) return "object is not a thing" - class Matcher: + def get_details(self): + return {} + class Matcher(object): def match(self, thing): calls.append(('match', thing)) return Mismatch(thing) @@ -282,6 +448,35 @@ class TestAssertions(TestCase): ], calls) self.assertFalse(result.wasSuccessful()) + def test_assertEqual_nice_formatting(self): + message = "These things ought not be equal." + a = ['apple', 'banana', 'cherry'] + b = {'Thatcher': 'One who mends roofs of straw', + 'Major': 'A military officer, ranked below colonel', + 'Blair': 'To shout loudly', + 'Brown': 'The colour of healthy human faeces'} + expected_error = '\n'.join( + [message, + 'not equal:', + 'a = %s' % pformat(a), + 'b = %s' % pformat(b), + '']) + self.assertFails(expected_error, self.assertEqual, a, b, message) + self.assertFails(expected_error, self.assertEquals, a, b, message) + self.assertFails(expected_error, self.failUnlessEqual, a, b, message) + + def test_assertEqual_formatting_no_message(self): + a = "cat" + b = "dog" + expected_error = '\n'.join( + ['not equal:', + 'a = %s' % pformat(a), + 'b = %s' % pformat(b), + '']) + self.assertFails(expected_error, self.assertEqual, a, b) + self.assertFails(expected_error, self.assertEquals, a, b) + self.assertFails(expected_error, self.failUnlessEqual, a, b) + class TestAddCleanup(TestCase): """Tests for TestCase.addCleanup.""" @@ -301,6 +496,9 @@ class TestAddCleanup(TestCase): def runTest(self): self._calls.append('runTest') + def brokenTest(self): + raise RuntimeError('Deliberate broken test') + def tearDown(self): self._calls.append('tearDown') TestCase.tearDown(self) @@ -400,13 +598,29 @@ class TestAddCleanup(TestCase): self.assertRaises( KeyboardInterrupt, self.test.run, self.logging_result) - def test_multipleErrorsReported(self): - # Errors from all failing cleanups are reported. + def test_multipleCleanupErrorsReported(self): + # Errors from all failing cleanups are reported as separate backtraces. + self.test.addCleanup(lambda: 1/0) + self.test.addCleanup(lambda: 1/0) + self.logging_result = ExtendedTestResult() + self.test.run(self.logging_result) + self.assertEqual(['startTest', 'addError', 'stopTest'], + [event[0] for event in self.logging_result._events]) + self.assertEqual(set(['traceback', 'traceback-1']), + set(self.logging_result._events[1][2].keys())) + + def test_multipleErrorsCoreAndCleanupReported(self): + # Errors from all failing cleanups are reported, with stopTest, + # startTest inserted. + self.test = TestAddCleanup.LoggingTest('brokenTest') self.test.addCleanup(lambda: 1/0) self.test.addCleanup(lambda: 1/0) + self.logging_result = ExtendedTestResult() self.test.run(self.logging_result) - self.assertErrorLogEqual( - ['startTest', 'addError', 'addError', 'stopTest']) + self.assertEqual(['startTest', 'addError', 'stopTest'], + [event[0] for event in self.logging_result._events]) + self.assertEqual(set(['traceback', 'traceback-1', 'traceback-2']), + set(self.logging_result._events[1][2].keys())) class TestWithDetails(TestCase): @@ -594,6 +808,61 @@ class TestDetailsProvided(TestWithDetails): self.assertDetailsProvided(Case("test"), "addUnexpectedSuccess", ["foo"]) + def test_addDetails_from_Mismatch(self): + content = self.get_content() + class Mismatch(object): + def describe(self): + return "Mismatch" + def get_details(self): + return {"foo": content} + class Matcher(object): + def match(self, thing): + return Mismatch() + def __str__(self): + return "a description" + class Case(TestCase): + def test(self): + self.assertThat("foo", Matcher()) + self.assertDetailsProvided(Case("test"), "addFailure", + ["foo", "traceback"]) + + def test_multiple_addDetails_from_Mismatch(self): + content = self.get_content() + class Mismatch(object): + def describe(self): + return "Mismatch" + def get_details(self): + return {"foo": content, "bar": content} + class Matcher(object): + def match(self, thing): + return Mismatch() + def __str__(self): + return "a description" + class Case(TestCase): + def test(self): + self.assertThat("foo", Matcher()) + self.assertDetailsProvided(Case("test"), "addFailure", + ["bar", "foo", "traceback"]) + + def test_addDetails_with_same_name_as_key_from_get_details(self): + content = self.get_content() + class Mismatch(object): + def describe(self): + return "Mismatch" + def get_details(self): + return {"foo": content} + class Matcher(object): + def match(self, thing): + return Mismatch() + def __str__(self): + return "a description" + class Case(TestCase): + def test(self): + self.addDetail("foo", content) + self.assertThat("foo", Matcher()) + self.assertDetailsProvided(Case("test"), "addFailure", + ["foo", "foo-1", "traceback"]) + class TestSetupTearDown(TestCase): @@ -624,6 +893,9 @@ class TestSkipping(TestCase): def test_skip_causes_skipException(self): self.assertRaises(self.skipException, self.skip, "Skip this test") + def test_can_use_skipTest(self): + self.assertRaises(self.skipException, self.skipTest, "Skip this test") + def test_skip_without_reason_works(self): class Test(TestCase): def test(self): @@ -750,6 +1022,64 @@ class TestOnException(TestCase): self.assertThat(events, Equals([])) +class TestPatchSupport(TestCase): + + class Case(TestCase): + def test(self): + pass + + def test_patch(self): + # TestCase.patch masks obj.attribute with the new value. + self.foo = 'original' + test = self.Case('test') + test.patch(self, 'foo', 'patched') + self.assertEqual('patched', self.foo) + + def test_patch_restored_after_run(self): + # TestCase.patch masks obj.attribute with the new value, but restores + # the original value after the test is finished. + self.foo = 'original' + test = self.Case('test') + test.patch(self, 'foo', 'patched') + test.run() + self.assertEqual('original', self.foo) + + def test_successive_patches_apply(self): + # TestCase.patch can be called multiple times per test. Each time you + # call it, it overrides the original value. + self.foo = 'original' + test = self.Case('test') + test.patch(self, 'foo', 'patched') + test.patch(self, 'foo', 'second') + self.assertEqual('second', self.foo) + + def test_successive_patches_restored_after_run(self): + # TestCase.patch restores the original value, no matter how many times + # it was called. + self.foo = 'original' + test = self.Case('test') + test.patch(self, 'foo', 'patched') + test.patch(self, 'foo', 'second') + test.run() + self.assertEqual('original', self.foo) + + def test_patch_nonexistent_attribute(self): + # TestCase.patch can be used to patch a non-existent attribute. + test = self.Case('test') + test.patch(self, 'doesntexist', 'patched') + self.assertEqual('patched', self.doesntexist) + + def test_restore_nonexistent_attribute(self): + # TestCase.patch can be used to patch a non-existent attribute, after + # the test run, the attribute is then removed from the object. + test = self.Case('test') + test.patch(self, 'doesntexist', 'patched') + test.run() + marker = object() + value = getattr(self, 'doesntexist', marker) + self.assertIs(marker, value) + + def test_suite(): from unittest import TestLoader return TestLoader().loadTestsFromName(__name__) |