← Back to team overview

testtools-dev team mailing list archive

[Merge] lp:~jml/testtools/extract-factory into lp:testtools

 

Jonathan Lange has proposed merging lp:~jml/testtools/extract-factory into lp:testtools.

Requested reviews:
  testtools committers (testtools-committers)

For more details, see:
https://code.launchpad.net/~jml/testtools/extract-factory/+merge/85833

This branch follows the advice that I recall receiving from Rob ages ago: it extracts a separate factory class out of TestCase.

It's fairly straightforward, but there are a few questions that came to mind while I was doing the work:

testtools/factory.py:11:
  XXX: Are we happy with the name ObjectFactory?

testtools/factory.py:13:
  XXX: Is this a good opportunity to change the getUniqueInteger and
  getUniqueString methods here to be get_unique_integer and get_unique_string?

testtools/testcase.py:196:
  XXX: We could have a class variable factory_factory (except with a
  better name) and then instead write:
      self.factory = self.factory_factory(self)
  Which would allow others to plugin in their factories more easily,
  rather than using the TestCaseWithFactory mixin pattern. Just a
  thought.

Would be interested in opinions.

jml
-- 
https://code.launchpad.net/~jml/testtools/extract-factory/+merge/85833
Your team testtools developers is subscribed to branch lp:testtools.
=== modified file 'NEWS'
--- NEWS	2011-12-05 15:33:37 +0000
+++ NEWS	2011-12-15 11:18:22 +0000
@@ -14,6 +14,14 @@
   ``MatchesAll`` with keyword arguments, then this change might affect your
   test results.  (Jonathan Lange)
 
+* ``TestCase`` now has a ``factory`` attribute, set to an instance of
+  ``ObjectFactory``.  It now uses this instance for generating unique strings
+  and integers.  (Jonathan Lange)
+
+* ``TestCase.getUniqueInteger`` and ``TestCase.getUniqueString`` are now
+  deprecated.  Use ``TestCase.factory.getUniqueInteger`` and
+  ``TestCase.factory.getUniqueString`` instead.  (Jonathan Lange)
+
 Improvements
 ------------
 

=== modified file 'doc/for-test-authors.rst'
--- doc/for-test-authors.rst	2011-12-07 11:32:45 +0000
+++ doc/for-test-authors.rst	2011-12-15 11:18:22 +0000
@@ -1240,17 +1240,19 @@
 fine.  However, sometimes it's useful to be able to create arbitrary objects
 at will, without having to make up silly sample data.
 
-To help with this, ``testtools.TestCase`` implements creation methods called
-``getUniqueString`` and ``getUniqueInteger``.  They return strings and
-integers that are unique within the context of the test that can be used to
-assemble more complex objects.  Here's a basic example where
-``getUniqueString`` is used instead of saying "foo" or "bar" or whatever::
+To help with this, ``testtools.TestCase`` comes with an ``ObjectFactory`` that
+you can access as the ``factory`` attribute within your test
+case. ``ObjectFactory`` implements creation methods called ``getUniqueString``
+and ``getUniqueInteger``.  They return strings and integers that are unique
+within the context of the test that can be used to assemble more complex
+objects.  Here's a basic example where ``getUniqueString`` is used instead of
+saying "foo" or "bar" or whatever::
 
   class SomeTest(TestCase):
 
       def test_full_name(self):
-          first_name = self.getUniqueString()
-          last_name = self.getUniqueString()
+          first_name = self.factory.getUniqueString()
+          last_name = self.factory.getUniqueString()
           p = Person(first_name, last_name)
           self.assertEqual(p.full_name, "%s %s" % (first_name, last_name))
 
@@ -1260,13 +1262,15 @@
   class TestCoupleLogic(TestCase):
 
       def make_arbitrary_person(self):
-          return Person(self.getUniqueString(), self.getUniqueString())
+          return Person(
+              self.factory.getUniqueString(),
+              self.factory.getUniqueString())
 
       def test_get_invitation(self):
           a = self.make_arbitrary_person()
           b = self.make_arbitrary_person()
           couple = Couple(a, b)
-          event_name = self.getUniqueString()
+          event_name = self.factory.getUniqueString()
           invitation = couple.get_invitation(event_name)
           self.assertEqual(
               invitation,

=== modified file 'testtools/__init__.py'
--- testtools/__init__.py	2011-09-14 10:36:41 +0000
+++ testtools/__init__.py	2011-12-15 11:18:22 +0000
@@ -12,6 +12,7 @@
     'iterate_tests',
     'MultipleExceptions',
     'MultiTestResult',
+    'ObjectFactory',
     'PlaceHolder',
     'run_test_with',
     'TestCase',
@@ -27,6 +28,7 @@
     'try_imports',
     ]
 
+from testtools.factory import ObjectFactory
 from testtools.helpers import (
     try_import,
     try_imports,

=== added file 'testtools/factory.py'
--- testtools/factory.py	1970-01-01 00:00:00 +0000
+++ testtools/factory.py	2011-12-15 11:18:22 +0000
@@ -0,0 +1,48 @@
+# Copyright (c) 2008-2011 testtools developers. See LICENSE for details.
+
+__all__ = [
+    'ObjectFactory',
+    ]
+
+import itertools
+
+from testtools.compat import advance_iterator
+
+# XXX: Are we happy with the name ObjectFactory?
+
+# XXX: Is this a good opportunity to change the getUniqueInteger and
+# getUniqueString methods here to be get_unique_integer and get_unique_string?
+
+class ObjectFactory(object):
+
+    DEFAULT_PREFIX = 'unique'
+
+    def __init__(self, prefix=None):
+        if prefix is None:
+            prefix = self.DEFAULT_PREFIX
+        self._prefix = prefix
+        self._unique_id_gen = itertools.count(1)
+
+    def getUniqueInteger(self):
+        """Get an integer unique to this test.
+
+        Returns an integer that is guaranteed to be unique to this instance.
+        Use this when you need an arbitrary integer in your test, or as a
+        helper for custom anonymous factory methods.
+        """
+        return advance_iterator(self._unique_id_gen)
+
+    def getUniqueString(self, prefix=None):
+        """Get a string unique to this test.
+
+        Returns a string that is guaranteed to be unique to this instance. Use
+        this when you need an arbitrary string in your test, or as a helper
+        for custom anonymous factory methods.
+
+        :param prefix: The prefix of the string. If not provided, defaults
+            to the id of the tests.
+        :return: A bytestring of '<prefix>-<unique_int>'.
+        """
+        if prefix is None:
+            prefix = self._prefix
+        return '%s-%d' % (prefix, self.getUniqueInteger())

=== modified file 'testtools/testcase.py'
--- testtools/testcase.py	2011-12-05 15:21:33 +0000
+++ testtools/testcase.py	2011-12-15 11:18:22 +0000
@@ -28,6 +28,7 @@
     advance_iterator,
     reraise,
     )
+from testtools.factory import ObjectFactory
 from testtools.matchers import (
     Annotate,
     Contains,
@@ -168,7 +169,6 @@
         runTest = kwargs.pop('runTest', None)
         super(TestCase, self).__init__(*args, **kwargs)
         self._cleanups = []
-        self._unique_id_gen = itertools.count(1)
         # Generators to ensure unique traceback ids.  Maps traceback label to
         # iterators.
         self._traceback_id_gens = {}
@@ -193,6 +193,13 @@
         if sys.version_info < (2, 6):
             # Catch old-style string exceptions with None as the instance
             self.exception_handlers.append((type(None), self._report_error))
+        # XXX: We could have a class variable factory_factory (except with a
+        # better name) and then instead write:
+        #     self.factory = self.factory_factory(self)
+        # Which would allow others to plugin in their factories more easily,
+        # rather than using the TestCaseWithFactory mixin pattern. Just a
+        # thought.
+        self.factory = ObjectFactory(self.id())
 
     def __eq__(self, other):
         eq = getattr(unittest.TestCase, '__eq__', None)
@@ -446,16 +453,20 @@
             raise _UnexpectedSuccess(reason)
 
     def getUniqueInteger(self):
-        """Get an integer unique to this test.
+        """DEPRECATED - Get an integer unique to this test.
+
+        Use ``self.factory.getUniqueInteger()`` instead.
 
         Returns an integer that is guaranteed to be unique to this instance.
         Use this when you need an arbitrary integer in your test, or as a
         helper for custom anonymous factory methods.
         """
-        return advance_iterator(self._unique_id_gen)
+        return self.factory.getUniqueInteger()
 
     def getUniqueString(self, prefix=None):
-        """Get a string unique to this test.
+        """DEPRECATED - Get a string unique to this test.
+
+        Use ``self.factory.getUniqueString()`` instead.
 
         Returns a string that is guaranteed to be unique to this instance. Use
         this when you need an arbitrary string in your test, or as a helper
@@ -465,9 +476,7 @@
             to the id of the tests.
         :return: A bytestring of '<prefix>-<unique_int>'.
         """
-        if prefix is None:
-            prefix = self.id()
-        return '%s-%d' % (prefix, self.getUniqueInteger())
+        return self.factory.getUniqueString(prefix=prefix)
 
     def onException(self, exc_info, tb_label='traceback'):
         """Called when an exception propogates from test code.

=== modified file 'testtools/tests/__init__.py'
--- testtools/tests/__init__.py	2011-08-15 13:48:10 +0000
+++ testtools/tests/__init__.py	2011-12-15 11:18:22 +0000
@@ -12,6 +12,7 @@
         test_content_type,
         test_deferredruntest,
         test_distutilscmd,
+        test_factory,
         test_fixturesupport,
         test_helpers,
         test_matchers,
@@ -29,6 +30,7 @@
         test_content_type,
         test_deferredruntest,
         test_distutilscmd,
+        test_factory,
         test_fixturesupport,
         test_helpers,
         test_matchers,

=== added file 'testtools/tests/test_factory.py'
--- testtools/tests/test_factory.py	1970-01-01 00:00:00 +0000
+++ testtools/tests/test_factory.py	2011-12-15 11:18:22 +0000
@@ -0,0 +1,47 @@
+from testtools import TestCase
+from testtools.factory import ObjectFactory
+from testtools.matchers import Equals
+
+
+class TestFactory(TestCase):
+
+    def test_getUniqueInteger(self):
+        # getUniqueInteger returns an integer that increments each time you
+        # call it.
+        factory = ObjectFactory()
+        one = factory.getUniqueInteger()
+        self.assertEqual(1, one)
+        two = factory.getUniqueInteger()
+        self.assertEqual(2, two)
+
+    def test_getUniqueString_default_prefix(self):
+        # If no other parameters are given, getUniqueString returns a string
+        # starting with the default prefix followed by a unique integer.
+        factory = ObjectFactory()
+        name_one = factory.getUniqueString()
+        self.assertEqual('%s-%d' % (factory.DEFAULT_PREFIX, 1), name_one)
+        name_two = factory.getUniqueString()
+        self.assertEqual('%s-%d' % (factory.DEFAULT_PREFIX, 2), name_two)
+
+    def test_getUniqueString_early_prefix(self):
+        # An optional prefix can be given to the factory.  If so, then all
+        # getUniqueString calls use that as a prefix by default.
+        factory = ObjectFactory(prefix=self.id())
+        name_one = factory.getUniqueString()
+        self.assertEqual('%s-%d' % (self.id(), 1), name_one)
+        name_two = factory.getUniqueString()
+        self.assertEqual('%s-%d' % (self.id(), 2), name_two)
+
+    def test_getUniqueString_late_prefix(self):
+        # If getUniqueString is given an argument, it uses that argument as
+        # the prefix of the unique string, rather than the test id.
+        factory = ObjectFactory()
+        name_one = factory.getUniqueString('foo')
+        self.assertThat(name_one, Equals('foo-1'))
+        name_two = factory.getUniqueString('bar')
+        self.assertThat(name_two, Equals('bar-2'))
+
+
+def test_suite():
+    from unittest import TestLoader
+    return TestLoader().loadTestsFromName(__name__)


Follow ups