testtools-dev team mailing list archive
-
testtools-dev team
-
Mailing list archive
-
Message #00021
[Merge] lp:~jml/testtools/patch-310770 into lp:testtools
Jonathan Lange has proposed merging lp:~jml/testtools/patch-310770 into lp:testtools.
Requested reviews:
testtools developers (testtools-dev)
More and more I've found myself wanting to monkey-patch code with testtools in the same way I can for Trial. This branch adds support for just such a thing.
--
https://code.launchpad.net/~jml/testtools/patch-310770/+merge/31666
Your team testtools developers is requested to review the proposed merge of lp:~jml/testtools/patch-310770 into lp:testtools.
=== modified file 'LICENSE'
--- LICENSE 2010-06-10 22:13:14 +0000
+++ LICENSE 2010-08-03 18:09:43 +0000
@@ -1,4 +1,5 @@
-Copyright (c) 2008 Jonathan M. Lange <jml@xxxxxxxxx> and the testtools authors.
+Copyright (c) 2008-2010 Jonathan M. Lange <jml@xxxxxxxxx> and the testtools
+authors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
=== modified file 'MANUAL'
--- MANUAL 2010-06-15 02:58:47 +0000
+++ MANUAL 2010-08-03 18:09:43 +0000
@@ -52,6 +52,24 @@
more data (via the addDetails API) and potentially other uses.
+TestCase.patch
+~~~~~~~~~~~~~~
+
+``patch`` is a convenient way to monkey-patch a Python object for the duration
+of your test. It's especially useful for testing legacy code. e.g.::
+
+ def test_foo(self):
+ my_stream = StringIO()
+ self.patch(sys, 'stderr', my_stream)
+ run_some_code_that_prints_to_stderr()
+ self.assertEqual('', my_stream.getvalue())
+
+The call to ``patch`` above masks sys.stderr with 'my_stream' so that anything
+printed to stderr will be captured in a StringIO variable that can be actually
+tested. Once the test is done, the real sys.stderr is restored to its rightful
+place.
+
+
TestCase.skipTest
~~~~~~~~~~~~~~~~~
=== modified file 'testtools/testcase.py'
--- testtools/testcase.py 2010-07-29 18:20:02 +0000
+++ testtools/testcase.py 2010-08-03 18:09:43 +0000
@@ -1,4 +1,4 @@
-# Copyright (c) 2008, 2009 Jonathan M. Lange. See LICENSE for details.
+# Copyright (c) 2008-2010 Jonathan M. Lange. See LICENSE for details.
"""Test case related stuff."""
@@ -121,6 +121,19 @@
"""
return self.__details
+ def patch(self, obj, attribute, value):
+ """Monkey-patch 'obj.attribute' to 'value' while the test is running.
+
+ :param obj: The object to patch. Can be anything.
+ :param attribute: The attribute on 'obj' to patch.
+ :param value: The value to set 'obj.attribute' to.
+ :return: The current value of 'obj.attribute'.
+ """
+ current = getattr(obj, attribute)
+ setattr(obj, attribute, value)
+ self.addCleanup(setattr, obj, attribute, current)
+ return current
+
def shortDescription(self):
return self.id()
@@ -137,7 +150,7 @@
"""
raise self.skipException(reason)
- # skipTest is how python2.7 spells this. Sometime in the future
+ # skipTest is how python2.7 spells this. Sometime in the future
# This should be given a deprecation decorator - RBC 20100611.
skip = skipTest
@@ -444,7 +457,7 @@
def clone_test_with_new_id(test, new_id):
"""Copy a TestCase, and give the copied test a new id.
-
+
This is only expected to be used on tests that have been constructed but
not executed.
"""
=== modified file 'testtools/tests/test_testtools.py'
--- testtools/tests/test_testtools.py 2010-07-29 12:20:37 +0000
+++ testtools/tests/test_testtools.py 2010-08-03 18:09:43 +0000
@@ -829,6 +829,56 @@
self.assertThat(events, Equals([]))
+class TestPatchSupport(TestCase):
+
+ class Case(TestCase):
+ def test(self):
+ pass
+
+ def test_patch(self):
+ # TestCase.patch masks obj.attribute with the new value.
+ self.foo = 'original'
+ test = self.Case('test')
+ test.patch(self, 'foo', 'patched')
+ self.assertEqual('patched', self.foo)
+
+ def test_patch_restored_after_run(self):
+ # TestCase.patch masks obj.attribute with the new value, but restores
+ # the original value after the test is finished.
+ self.foo = 'original'
+ test = self.Case('test')
+ test.patch(self, 'foo', 'patched')
+ test.run()
+ self.assertEqual('original', self.foo)
+
+ def test_patch_returns_original(self):
+ # TestCase.patch returns the current value of the attribute being
+ # patched, just in case you want to do something with it.
+ self.foo = 'original'
+ test = self.Case('test')
+ result = test.patch(self, 'foo', 'patched')
+ self.assertEqual('original', result)
+
+ def test_successive_patches_apply(self):
+ # TestCase.patch can be called multiple times per test. Each time you
+ # call it, it overrides the original value.
+ self.foo = 'original'
+ test = self.Case('test')
+ test.patch(self, 'foo', 'patched')
+ test.patch(self, 'foo', 'second')
+ self.assertEqual('second', self.foo)
+
+ def test_successive_patches_restored_after_run(self):
+ # TestCase.patch restores the original value, no matter how many times
+ # it was called.
+ self.foo = 'original'
+ test = self.Case('test')
+ test.patch(self, 'foo', 'patched')
+ test.patch(self, 'foo', 'second')
+ test.run()
+ self.assertEqual('original', self.foo)
+
+
def test_suite():
from unittest import TestLoader
return TestLoader().loadTestsFromName(__name__)
Follow ups