← Back to team overview

testtools-dev team mailing list archive

[Merge] lp:~jml/testtools/placeholder-test into lp:testtools

 

Jonathan Lange has proposed merging lp:~jml/testtools/placeholder-test into lp:testtools.

Requested reviews:
  testtools developers (testtools-dev)


This branch adds a couple of TestCase-like objects that can be used to add results to a TestResult. One is for a successful test, the other is for an erroring test.

A good use case for ErrorHolder is trapping import errors from a test loader and adding them to the TestResult as failing tests.

It's debatable whether the placeholders should count as 1 test or 0 tests. I chose 1 because it's less weird, but Twisted choses 0.

I guess the tests, ideally, should be more like interface verification tests.
-- 
https://code.launchpad.net/~jml/testtools/placeholder-test/+merge/33232
Your team testtools developers is requested to review the proposed merge of lp:~jml/testtools/placeholder-test into lp:testtools.
=== modified file 'NEWS'
--- NEWS	2010-08-17 13:08:01 +0000
+++ NEWS	2010-08-20 15:42:48 +0000
@@ -7,6 +7,9 @@
 Improvements
 ------------
 
+ * Added `PlaceHolder` and `ErrorHolder`, TestCase-like objects that can be
+   used to add results to a `TestResult`.
+
  * 'Mismatch' now takes optional description and details parameters, so
    custom Matchers aren't compelled to make their own subclass.
 

=== modified file 'testtools/__init__.py'
--- testtools/__init__.py	2010-07-29 18:48:37 +0000
+++ testtools/__init__.py	2010-08-20 15:42:48 +0000
@@ -5,9 +5,11 @@
 __all__ = [
     'clone_test_with_new_id',
     'ConcurrentTestSuite',
+    'ErrorHolder',
     'ExtendedToOriginalDecorator',
     'iterate_tests',
     'MultiTestResult',
+    'PlaceHolder',
     'TestCase',
     'TestResult',
     'TextTestResult',
@@ -25,6 +27,8 @@
     RunTest,
     )
 from testtools.testcase import (
+    ErrorHolder,
+    PlaceHolder,
     TestCase,
     clone_test_with_new_id,
     skip,

=== modified file 'testtools/testcase.py'
--- testtools/testcase.py	2010-08-04 13:05:20 +0000
+++ testtools/testcase.py	2010-08-20 15:42:48 +0000
@@ -450,12 +450,99 @@
         self.__teardown_called = True
 
 
+class PlaceHolder(object):
+    """A placeholder test.
+
+    `PlaceHolder` implements much of the same interface as `TestCase` and is
+    particularly suitable for being added to `TestResult`s.
+    """
+
+    def __init__(self, test_id, short_description=None):
+        """Construct a `PlaceHolder`.
+
+        :param test_id: The id of the placeholder test.
+        :param short_description: The short description of the place holder
+            test. If not provided, the id will be used instead.
+        """
+        self._test_id = test_id
+        self._short_description = short_description
+
+    def __call__(self, result=None):
+        return self.run(result=result)
+
+    def __repr__(self):
+        internal = [self._test_id]
+        if self._short_description is not None:
+            internal.append(self._short_description)
+        return "<%s.%s(%s)>" % (
+            self.__class__.__module__,
+            self.__class__.__name__,
+            ", ".join(map(repr, internal)))
+
+    def __str__(self):
+        return self.id()
+
+    def countTestCases(self):
+        return 1
+
+    def debug(self):
+        pass
+
+    def id(self):
+        return self._test_id
+
+    def run(self, result=None):
+        if result is None:
+            result = TestResult()
+        result.startTest(self)
+        result.addSuccess(self)
+        result.stopTest(self)
+
+    def shortDescription(self):
+        if self._short_description is None:
+            return self.id()
+        else:
+            return self._short_description
+
+
+class ErrorHolder(PlaceHolder):
+    """A placeholder test that will error out when run."""
+
+    failureException = None
+
+    def __init__(self, test_id, error, short_description=None):
+        """Construct an `ErrorHolder`.
+
+        :param test_id: The id of the test.
+        :param error: The exc info tuple that will be used as the test's error.
+        :param short_description: An optional short description of the test.
+        """
+        super(ErrorHolder, self).__init__(
+            test_id, short_description=short_description)
+        self._error = error
+
+    def __repr__(self):
+        internal = [self._test_id, self._error]
+        if self._short_description is not None:
+            internal.append(self._short_description)
+        return "<%s.%s(%s)>" % (
+            self.__class__.__module__,
+            self.__class__.__name__,
+            ", ".join(map(repr, internal)))
+
+    def run(self, result=None):
+        if result is None:
+            result = TestResult()
+        result.startTest(self)
+        result.addError(self, self._error)
+        result.stopTest(self)
+
+
 # Python 2.4 did not know how to copy functions.
 if types.FunctionType not in copy._copy_dispatch:
     copy._copy_dispatch[types.FunctionType] = copy._copy_immutable
 
 
-
 def clone_test_with_new_id(test, new_id):
     """Copy a TestCase, and give the copied test a new id.
 

=== modified file 'testtools/tests/test_testtools.py'
--- testtools/tests/test_testtools.py	2010-08-04 13:05:20 +0000
+++ testtools/tests/test_testtools.py	2010-08-20 15:42:48 +0000
@@ -1,4 +1,4 @@
-# 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."""
 
@@ -6,6 +6,8 @@
 import unittest
 
 from testtools import (
+    ErrorHolder,
+    PlaceHolder,
     TestCase,
     clone_test_with_new_id,
     content,
@@ -26,6 +28,167 @@
     )
 
 
+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."""
 


Follow ups