From 0f874b117bfef424da33c50fccd66e9c2b8a74f8 Mon Sep 17 00:00:00 2001 From: Thomi Richards Date: Thu, 21 Nov 2013 10:48:00 +1300 Subject: [PATCH] Add 'force_failure' feature to testtools.TestCase. Change-Id: Idb8c8d0c837ab2cb6ec53c84d144c29b5d6da8ea --- NEWS | 3 +++ doc/for-framework-folk.rst | 9 +++++++++ doc/for-test-authors.rst | 7 +++++++ testtools/runtest.py | 7 +++++++ testtools/testcase.py | 4 +++- testtools/tests/test_testcase.py | 10 ++++++++++ 6 files changed, 39 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index e77a202..5fa80a5 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,9 @@ Improvements exiting (2) when an import has failed rather than only signalling through the test name. (Robert Collins, #1245672) +* Added ability for ``testtools.TestCase`` instances to force a test to + fail, even if no assertions failed. (Thomi Richards) + * Added ``testtools.content.StacktraceContent``, a content object that automatically creates a ``StackLinesContent`` object containing the current stack trace. (Thomi Richards) diff --git a/doc/for-framework-folk.rst b/doc/for-framework-folk.rst index 91bfa2c..e162859 100644 --- a/doc/for-framework-folk.rst +++ b/doc/for-framework-folk.rst @@ -82,6 +82,15 @@ Test renaming instance to one with a new name. This is helpful for implementing test parameterization. +.. _force_failure: + +Delayed Test Failure +-------------------- + +Setting the ``testtools.TestCase.force_failure`` instance variable to True will +cause ``testtools.RunTest`` to fail the test case after the test has finished. +This is useful when you want to cause a test to fail, but don't want to +prevent the remainder of the test code from being executed. Test placeholders ================= diff --git a/doc/for-test-authors.rst b/doc/for-test-authors.rst index 06747a6..03849e6 100644 --- a/doc/for-test-authors.rst +++ b/doc/for-test-authors.rst @@ -1243,6 +1243,13 @@ Here are some tips for converting your Trial tests into testtools tests. ``AsynchronousDeferredRunTest`` does not. If you rely on this behavior, use ``AsynchronousDeferredRunTestForBrokenTwisted``. +force_failure +------------- + +Setting the ``testtools.TestCase.force_failure`` instance variable to ``True`` +will cause the test to be marked as a failure, but won't stop the test code +from running (see :ref:`force_failure`). + Test helpers ============ diff --git a/testtools/runtest.py b/testtools/runtest.py index 507ad87..26ae387 100644 --- a/testtools/runtest.py +++ b/testtools/runtest.py @@ -135,6 +135,9 @@ class RunTest(object): self._run_cleanups, self.result): failed = True finally: + if getattr(self.case, 'force_failure', None): + self._run_user(_raise_force_fail_error) + failed = True if not failed: self.result.addSuccess(self.case, details=self.case.getDetails()) @@ -200,6 +203,10 @@ class RunTest(object): raise e +def _raise_force_fail_error(): + raise AssertionError("Forced Test Failure") + + # Signal that this is part of the testing framework, and that code from this # should not normally appear in tracebacks. __unittest = True diff --git a/testtools/testcase.py b/testtools/testcase.py index 13b71df..59ea205 100644 --- a/testtools/testcase.py +++ b/testtools/testcase.py @@ -155,6 +155,8 @@ class TestCase(unittest.TestCase): :ivar exception_handlers: Exceptions to catch from setUp, runTest and tearDown. This list is able to be modified at any time and consists of (exception_class, handler(case, result, exception_value)) pairs. + :ivar force_failure: Force testtools.RunTest to fail the test after the + test has completed. :cvar run_tests_with: A factory to make the ``RunTest`` to run tests with. Defaults to ``RunTest``. The factory is expected to take a test case and an optional list of exception handlers. @@ -924,7 +926,7 @@ class DecorateTestCaseResult(object): def __getattr__(self, name): return getattr(self.decorated, name) - + def __delattr__(self, name): delattr(self.decorated, name) diff --git a/testtools/tests/test_testcase.py b/testtools/tests/test_testcase.py index a7faa59..680368d 100644 --- a/testtools/tests/test_testcase.py +++ b/testtools/tests/test_testcase.py @@ -569,6 +569,16 @@ class TestAssertions(TestCase): self.assertFails( expected, self.assertThat, matchee, matcher, verbose=True) + def test__force_failure_fails_test(self): + class Test(TestCase): + def test_foo(self): + self.force_failure = True + self.remaining_code_run = True + test = Test('test_foo') + result = test.run() + self.assertFalse(result.wasSuccessful()) + self.assertTrue(test.remaining_code_run) + def get_error_string(self, e): """Get the string showing how 'e' would be formatted in test output.