Clarify unittest documentation

- Update the description.
   Dangling mocks are no longer detected after
   every single test case.
 - Use a preffered pattern for unmocking.
 - Allow the 'patch_conf_property' take an optional
   'section' argument and add examples on configuration
    mocking to the doc.
 - Add extra examples on object mocking.

Change-Id: I9dc6d26ca879b0d5e1b16d1bdbc97b2a81351781
This commit is contained in:
Petr Malik 2016-07-15 14:19:06 -04:00
parent d58baf220a
commit e8783520e1
2 changed files with 61 additions and 22 deletions

View File

@ -39,45 +39,53 @@ on the runtime environment, making them difficult to debug.
There are several possible strategies available for dealing with dangling There are several possible strategies available for dealing with dangling
mock objects (see the section on recommended patterns). mock objects (see the section on recommended patterns).
Further information is available in [1]_. Further information is available in [1]_, [2]_, [3]_.
Dangling Mock Detector Dangling Mock Detector
---------------------- ----------------------
All Trove unit tests should extend 'trove_testtools.TestCase'. All Trove unit tests should extend 'trove_testtools.TestCase'.
It is a subclass of 'testtools.TestCase' which automatically checks for It is a subclass of 'testtools.TestCase' which automatically checks for
dangling mock objects after each test. dangling mock objects at each test class teardown.
It does that by recording mock instances in loaded modules before and after It marks the tests as failed and reports the leaked reference if it
a test case. It marks the test as failed and reports the leaked reference if it
finds any. finds any.
Recommended Mocking Patterns Recommended Mocking Patterns
---------------------------- ----------------------------
- Mocking a class or object shared across multiple test cases. Mocking a class or object shared across multiple test cases.
Use the patcher pattern in conjunction with the setUp() and tearDown() Use the patcher pattern in conjunction with the setUp()
methods [ see section 26.4.3.5. of [1]_ ]. method [ see section 26.4.3.5. of [1]_ ].
.. code-block:: python .. code-block:: python
def setUp(self): def setUp(self):
super(CouchbaseBackupTests, self).setUp() super(CouchbaseBackupTests, self).setUp()
self.exe_timeout_patch = patch.object(utils, 'execute_with_timeout') self.exe_timeout_patch = patch.object(utils, 'execute_with_timeout')
self.addCleanup(self.exe_timeout_patch.stop)
def test_case(self): def test_case(self):
# This line can be moved to the setUp() method if the mock object mock_exe_timeout = self.exe_timeout_patch.start()
# is not needed.
mock_object = self.exe_timeout_patch.start()
def tearDown(self): If the mock object is required in the majority of test cases the following
super(CouchbaseBackupTests, self).tearDown() pattern may be more efficient.
self.exe_timeout_patch.stop()
Note also: patch.stopall() .. code-block:: python
This method stops all active patches that were started with start.
- Mocking a class or object for a single entire test case. def setUp(self):
Use the decorator pattern. super(CouchbaseBackupTests, self).setUp()
self.exe_timeout_patch = patch.object(utils, 'execute_with_timeout')
self.addCleanup(self.exe_timeout_patch.stop)
self.mock_exe_timeout = self.exe_timeout_patch.start()
def test_case(self):
# All test cases can now reference 'self.mock_exe_timeout'.
- Note also: patch.stopall()
This method stops all active patches that were started with start.
Mocking a class or object for a single entire test case.
Use the decorator pattern.
.. code-block:: python .. code-block:: python
@ -91,8 +99,8 @@ This method stops all active patches that were started with start.
def test_case(self, generate_random_password, execute_with_timeout): def test_case(self, generate_random_password, execute_with_timeout):
pass pass
- Mocking a class or object for a smaller scope within one test case. Mocking a class or object for a smaller scope within one test case.
Use the context manager pattern. Use the context manager pattern.
.. code-block:: python .. code-block:: python
@ -109,7 +117,35 @@ This method stops all active patches that were started with start.
password_mock = mocks['generate_random_password'] password_mock = mocks['generate_random_password']
execute_mock = mocks['execute_with_timeout_mock'] execute_mock = mocks['execute_with_timeout_mock']
Mocking global configuration properties.
Use 'patch_conf_property' method from 'trove_testtools.TestCase'.
.. code-block:: python
def test_case(self):
self.patch_conf_property('max_accepted_volume_size', 10)
Datastore-specific configuration properties can be mocked by passing
an optional 'section' argument to the above call.
.. code-block:: python
def test_case(self):
self.patch_conf_property('cluster_support', False, section='redis')
- Note also: 'patch_datastore_manager()'
'datastore_manager' name has to be set properly when testing
datastore-specific code to ensure correct configuration options get loaded.
This is a convenience method for mocking 'datastore_manager' name.
.. code-block:: python
def test_case(self):
self.patch_datastore_manager('cassandra')
References References
---------- ----------
.. [1] Mock Guide: https://docs.python.org/3/library/unittest.mock.html .. [1] Mock Guide: https://docs.python.org/3/library/unittest.mock.html
.. [2] Python Mock Gotchas: http://alexmarandon.com/articles/python_mock_gotchas/
.. [3] Mocking Mistakes: http://engineroom.trackmaven.com/blog/mocking-mistakes/

View File

@ -166,9 +166,12 @@ class TestCase(testtools.TestCase):
def patch_datastore_manager(self, manager_name): def patch_datastore_manager(self, manager_name):
return self.patch_conf_property('datastore_manager', manager_name) return self.patch_conf_property('datastore_manager', manager_name)
def patch_conf_property(self, property_name, value): def patch_conf_property(self, property_name, value, section=None):
target = cfg.CONF
if section:
target = target.get(section)
conf_patcher = mock.patch.object( conf_patcher = mock.patch.object(
cfg.CONF, property_name, target, property_name,
new_callable=mock.PropertyMock(return_value=value)) new_callable=mock.PropertyMock(return_value=value))
self.addCleanup(conf_patcher.stop) self.addCleanup(conf_patcher.stop)
return conf_patcher.start() return conf_patcher.start()