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
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
----------------------
All Trove unit tests should extend 'trove_testtools.TestCase'.
It is a subclass of 'testtools.TestCase' which automatically checks for
dangling mock objects after each test.
It does that by recording mock instances in loaded modules before and after
a test case. It marks the test as failed and reports the leaked reference if it
dangling mock objects at each test class teardown.
It marks the tests as failed and reports the leaked reference if it
finds any.
Recommended Mocking Patterns
----------------------------
- Mocking a class or object shared across multiple test cases.
Use the patcher pattern in conjunction with the setUp() and tearDown()
methods [ see section 26.4.3.5. of [1]_ ].
Mocking a class or object shared across multiple test cases.
Use the patcher pattern in conjunction with the setUp()
method [ see section 26.4.3.5. of [1]_ ].
.. code-block:: python
def setUp(self):
super(CouchbaseBackupTests, self).setUp()
self.exe_timeout_patch = patch.object(utils, 'execute_with_timeout')
self.addCleanup(self.exe_timeout_patch.stop)
def test_case(self):
# This line can be moved to the setUp() method if the mock object
# is not needed.
mock_object = self.exe_timeout_patch.start()
mock_exe_timeout = self.exe_timeout_patch.start()
def tearDown(self):
super(CouchbaseBackupTests, self).tearDown()
self.exe_timeout_patch.stop()
If the mock object is required in the majority of test cases the following
pattern may be more efficient.
Note also: patch.stopall()
This method stops all active patches that were started with start.
.. code-block:: python
- Mocking a class or object for a single entire test case.
Use the decorator pattern.
def setUp(self):
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
@ -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):
pass
- Mocking a class or object for a smaller scope within one test case.
Use the context manager pattern.
Mocking a class or object for a smaller scope within one test case.
Use the context manager pattern.
.. code-block:: python
@ -109,7 +117,35 @@ This method stops all active patches that were started with start.
password_mock = mocks['generate_random_password']
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
----------
.. [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):
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(
cfg.CONF, property_name,
target, property_name,
new_callable=mock.PropertyMock(return_value=value))
self.addCleanup(conf_patcher.stop)
return conf_patcher.start()