dingus-0.3.4/0000755000076500000240000000000011730161472012706 5ustar grbstaff00000000000000dingus-0.3.4/dingus.egg-info/0000755000076500000240000000000011730161472015671 5ustar grbstaff00000000000000dingus-0.3.4/dingus.egg-info/dependency_links.txt0000644000076500000240000000000111730161472021737 0ustar grbstaff00000000000000 dingus-0.3.4/dingus.egg-info/PKG-INFO0000644000076500000240000001266611730161472017001 0ustar grbstaff00000000000000Metadata-Version: 1.0 Name: dingus Version: 0.3.4 Summary: A record-then-assert mocking library Home-page: https://github.com/garybernhardt/dingus Author: Gary Bernhardt Author-email: gary.bernhardt@gmail.com License: MIT Description: ======== DINGUSES ======== A dingus is sort of like a mock object. The main difference is that you don't set up expectations ahead of time. You just run your code, using a dingus in place of another object or class, and it will record what happens to it. Then, once your code has been exercised, you can make assertions about what it did to the dingus. A new dingus is created from the Dingus class. You can give dinguses names, which helps with debugging your tests, especially when there are multiple dinguses in play. >>> from dingus import Dingus >>> d = Dingus('root') >>> d Accessing any attribute of a dingus will return a new dingus. >>> d.something There are a few exceptions for special dingus methods. We'll see some in a bit. A dingus can also be called like a function or method. It doesn't care how many arguments you give it or what those arguments are. Calls to a dingus will always return the same object, regardless of the arguments. >>> d() >>> d('argument') >>> d(55) ======================== RECORDING AND ASSERTIONS ======================== At any time we can get a list of calls that have been made to a dingus. Each entry in the call list contains: * the name of the method called (or "()" if the dingus itself was called) * The arguments, or () if none * The keyword argumnets, or {} if none * The value that was returned to the caller Here is a list of the calls we've made to d so far: >>> from pprint import pprint >>> pprint(d.calls) [('()', (), {}, ), ('()', ('argument',), {}, ), ('()', (55,), {}, )] You can filter calls by name, arguments, and keyword arguments: >>> pprint(d.calls('()', 55)) [('()', (55,), {}, )] If you don't care about a particular argument's value, you can use the value DontCare when filtering: >>> from dingus import DontCare >>> pprint(d.calls('()', DontCare)) [('()', ('argument',), {}, ), ('()', (55,), {}, )] Dinguses can do more than just have attributes accessed and be called. They support many Python operators. The goal is to allow, and record, any interaction: >>> d = Dingus('root') >>> (2 ** d.something)['hello']() / 100 * 'foo' (Hopefully your real-world dingus recordings won't look like this!) ======== PATCHING ======== Dingus provides a context manager for patching objects during tests. For example: >>> from dingus import patch >>> import urllib2 >>> with patch('urllib2.urlopen'): ... print urllib2.urlopen.__class__ >>> print urllib2.urlopen.__class__ You can also use this as a decorator on your test methods: >>> @patch('urllib2.urlopen') ... def test_something(self): ... pass ... ========= ISOLATION ========= The opposite of patch is isolate. It patches everything except the named object: >>> from dingus import isolate >>> @isolate('urllib2.urlparse') ... def test_urlparse(self): ... pass ... When this test runs, everything in the urllib2 module except urlparse will be a dingus. Note that this may be slow to execute if the module contains many objects; performance patches are welcome. :) =============== DANGEROUS MAGIC =============== Dingus can also automatically replace a module's globals when running tests. This allows you to write fully isolated unit tests. See examples/urllib2/test\_urllib2.py for an example. The author no longer recommends this feature, as it can encourage very brittle tests. You should feel the pain of manually mocking dependencies; the pain will tell you when a class collaborates with too many others. Keywords: testing test mocking mock double stub fake record assert Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Testing dingus-0.3.4/dingus.egg-info/SOURCES.txt0000644000076500000240000000022511730161472017554 0ustar grbstaff00000000000000README.txt dingus.py setup.py dingus.egg-info/PKG-INFO dingus.egg-info/SOURCES.txt dingus.egg-info/dependency_links.txt dingus.egg-info/top_level.txtdingus-0.3.4/dingus.egg-info/top_level.txt0000644000076500000240000000000711730161472020420 0ustar grbstaff00000000000000dingus dingus-0.3.4/dingus.py0000644000076500000240000003075511730161143014556 0ustar grbstaff00000000000000import sys from functools import wraps def DingusTestCase(object_under_test, exclude=None): if isinstance(exclude, basestring): raise ValueError("Strings not allowed for exclude. " + "Use a list: exclude=['identifier']") exclude = [] if exclude is None else exclude def get_names_under_test(): module = sys.modules[object_under_test.__module__] for name, value in module.__dict__.iteritems(): if value is object_under_test or name in exclude: yield name class TestCase(object): def setup(self): module_name = object_under_test.__module__ self._dingus_module = sys.modules[module_name] self._dingus_replace_module_globals(self._dingus_module) def teardown(self): self._dingus_restore_module(self._dingus_module) def _dingus_replace_module_globals(self, module): old_module_dict = module.__dict__.copy() module_keys = set(module.__dict__.iterkeys()) dunders = set(k for k in module_keys if k.startswith('__') and k.endswith('__')) replaced_keys = (module_keys - dunders - set(names_under_test)) for key in replaced_keys: module.__dict__[key] = Dingus() module.__dict__['__dingused_dict__'] = old_module_dict def _dingus_restore_module(self, module): old_module_dict = module.__dict__['__dingused_dict__'] module.__dict__.clear() module.__dict__.update(old_module_dict) names_under_test = list(get_names_under_test()) TestCase.__name__ = '%s_DingusTestCase' % '_'.join(names_under_test) return TestCase # These sentinels are used for argument defaults because the user might want # to pass in None, which is different in some cases than passing nothing. class NoReturnValue(object): pass class NoArgument(object): pass def patch(object_path, new_object=NoArgument): module_name, attribute_name = object_path.rsplit('.', 1) return _Patcher(module_name, attribute_name, new_object) class _Patcher: def __init__(self, module_name, attribute_name, new_object): self.module_name = module_name self.attribute_name = attribute_name self.module = _importer(self.module_name) if new_object is NoArgument: full_name = '%s.%s' % (module_name, attribute_name) self.new_object = Dingus(full_name) else: self.new_object = new_object def __call__(self, fn): @wraps(fn) def new_fn(*args, **kwargs): self.patch_object() try: return fn(*args, **kwargs) finally: self.restore_object() return new_fn def __enter__(self): self.patch_object() def __exit__(self, exc_type, exc_value, traceback): self.restore_object() def patch_object(self): self.original_object = getattr(self.module, self.attribute_name) setattr(self.module, self.attribute_name, self.new_object) def restore_object(self): setattr(self.module, self.attribute_name, self.original_object) def isolate(object_path): def decorator(fn): module_name, object_name = object_path.rsplit('.', 1) module = sys.modules[module_name] neighbors = set(dir(module)) - set([object_name]) for neighbor in neighbors: neighbor_path = '%s.%s' % (module_name, neighbor) fn = patch(neighbor_path)(fn) return fn return decorator def _importer(target): components = target.split('.') import_path = components.pop(0) thing = __import__(import_path) for comp in components: import_path += ".%s" % comp thing = _dot_lookup(thing, comp, import_path) return thing def _dot_lookup(thing, comp, import_path): try: return getattr(thing, comp) except AttributeError: __import__(import_path) return getattr(thing, comp) class DontCare(object): pass class Call(tuple): def __new__(cls, name, args, kwargs, return_value): return tuple.__new__(cls, (name, args, kwargs, return_value)) def __init__(self, *args): self.name = self[0] self.args = self[1] self.kwargs = self[2] self.return_value = self[3] def __getnewargs__(self): return (self.name, self.args, self.kwargs, self.return_value) class CallList(list): @staticmethod def _match_args(call, args): if not args: return True elif len(args) != len(call.args): return False else: return all(args[i] in (DontCare, call.args[i]) for i in range(len(call.args))) @staticmethod def _match_kwargs(call, kwargs): if not kwargs: return True elif len(kwargs) != len(call.kwargs): return False else: return all(name in kwargs and kwargs[name] in (DontCare, val) for name, val in call.kwargs.iteritems()) def one(self): if len(self) == 1: return self[0] else: return None def once(self): return self.one() def __call__(self, __name=NoArgument, *args, **kwargs): return CallList([call for call in self if (__name is NoArgument or __name == call.name) and self._match_args(call, args) and self._match_kwargs(call, kwargs)]) def returner(return_value): return Dingus(return_value=return_value) class Dingus(object): @property def __enter__(self): return self._existing_or_new_child('__enter__') def __exit__(self, exc_type=None, exc_val=None, exc_tb=None): if exc_type and exc_type not in self.consumed_context_manager_exceptions: return False else: return True def __init__(self, dingus_name=None, full_name=None, consumed_context_manager_exceptions=None, **kwargs): self._parent = None self.reset() name = 'dingus_%i' % id(self) if dingus_name is None else dingus_name full_name = name if full_name is None else full_name self._short_name = name self._full_name = full_name self.__name__ = name self._full_name = full_name self.consumed_context_manager_exceptions = ( consumed_context_manager_exceptions or []) for attr_name, attr_value in kwargs.iteritems(): if attr_name.endswith('__returns'): attr_name = attr_name.replace('__returns', '') returner = self._create_child(attr_name) returner.return_value = attr_value setattr(self, attr_name, returner) else: setattr(self, attr_name, attr_value) self._replace_init_method() @classmethod def many(cls, count): return tuple(cls() for _ in range(count)) def _fake_init(self, *args, **kwargs): return self.__getattr__('__init__')(*args, **kwargs) def _replace_init_method(self): self.__init__ = self._fake_init def _create_child(self, name): separator = ('' if (name.startswith('()') or name.startswith('[')) else '.') full_name = self._full_name + separator + name child = self.__class__(name, full_name) child._parent = self return child def reset(self): self._return_value = NoReturnValue self.calls = CallList() self._children = {} def _get_return_value(self): if self._return_value is NoReturnValue: self._return_value = self._create_child('()') return self._return_value def _set_return_value(self, value): self._return_value = value return_value = property(_get_return_value, _set_return_value) def __call__(self, *args, **kwargs): self._log_call('()', args, kwargs, self.return_value) if self._parent: self._parent._log_call(self._short_name, args, kwargs, self.return_value) return self.return_value def _log_call(self, name, args, kwargs, return_value): self.calls.append(Call(name, args, kwargs, return_value)) def _should_ignore_attribute(self, name): return name in ['__pyobjc_object__', '__getnewargs__'] def __getstate__(self): # Python cannot pickle a instancemethod # http://bugs.python.org/issue558238 return [ (attr, value) for attr, value in self.__dict__.items() if attr != "__init__"] def __setstate__(self, state): self.__dict__.update(state) self._replace_init_method() def _existing_or_new_child(self, child_name, default_value=NoArgument): if child_name not in self._children: value = (self._create_child(child_name) if default_value is NoArgument else default_value) self._children[child_name] = value return self._children[child_name] def _remove_child_if_exists(self, child_name): if child_name in self._children: del self._children[child_name] def __getattr__(self, name): if self._should_ignore_attribute(name): raise AttributeError(name) return self._existing_or_new_child(name) def __delattr__(self, name): self._log_call('__delattr__', (name,), {}, None) def __getitem__(self, index): child_name = '[%s]' % (index,) return_value = self._existing_or_new_child(child_name) self._log_call('__getitem__', (index,), {}, return_value) return return_value def __setitem__(self, index, value): child_name = '[%s]' % (index,) self._log_call('__setitem__', (index, value), {}, None) self._remove_child_if_exists(child_name) self._existing_or_new_child(child_name, value) def _create_infix_operator(name): def operator_fn(self, other): return_value = self._existing_or_new_child(name) self._log_call(name, (other,), {}, return_value) return return_value operator_fn.__name__ = name return operator_fn _BASE_OPERATOR_NAMES = ['add', 'and', 'div', 'lshift', 'mod', 'mul', 'or', 'pow', 'rshift', 'sub', 'xor'] def _infix_operator_names(base_operator_names): # This function has to have base_operator_names passed in because # Python's scoping rules prevent it from seeing the class-level # _BASE_OPERATOR_NAMES. reverse_operator_names = ['r%s' % name for name in base_operator_names] for operator_name in base_operator_names + reverse_operator_names: operator_fn_name = '__%s__' % operator_name yield operator_fn_name # Define each infix operator for operator_fn_name in _infix_operator_names(_BASE_OPERATOR_NAMES): exec('%s = _create_infix_operator("%s")' % (operator_fn_name, operator_fn_name)) def _augmented_operator_names(base_operator_names): # Augmented operators are things like +=. They behavior differently # than normal infix operators because they return self instead of a # new object. return ['__i%s__' % operator_name for operator_name in base_operator_names] def _create_augmented_operator(name): def operator_fn(self, other): return_value = self self._log_call(name, (other,), {}, return_value) return return_value operator_fn.__name__ = name return operator_fn # Define each augmenting operator for operator_fn_name in _augmented_operator_names(_BASE_OPERATOR_NAMES): exec('%s = _create_augmented_operator("%s")' % (operator_fn_name, operator_fn_name)) def __str__(self): return '' % self._full_name __repr__ = __str__ def __len__(self): return 1 def __iter__(self): return iter([self._existing_or_new_child('__iter__')]) # We don't want to define __deepcopy__ at all. If there isn't one, deepcopy # will clone the whole object, which is what we want. __deepcopy__ = None def exception_raiser(exception): def raise_exception(*args, **kwargs): raise exception return raise_exception dingus-0.3.4/PKG-INFO0000644000076500000240000001266611730161472014016 0ustar grbstaff00000000000000Metadata-Version: 1.0 Name: dingus Version: 0.3.4 Summary: A record-then-assert mocking library Home-page: https://github.com/garybernhardt/dingus Author: Gary Bernhardt Author-email: gary.bernhardt@gmail.com License: MIT Description: ======== DINGUSES ======== A dingus is sort of like a mock object. The main difference is that you don't set up expectations ahead of time. You just run your code, using a dingus in place of another object or class, and it will record what happens to it. Then, once your code has been exercised, you can make assertions about what it did to the dingus. A new dingus is created from the Dingus class. You can give dinguses names, which helps with debugging your tests, especially when there are multiple dinguses in play. >>> from dingus import Dingus >>> d = Dingus('root') >>> d Accessing any attribute of a dingus will return a new dingus. >>> d.something There are a few exceptions for special dingus methods. We'll see some in a bit. A dingus can also be called like a function or method. It doesn't care how many arguments you give it or what those arguments are. Calls to a dingus will always return the same object, regardless of the arguments. >>> d() >>> d('argument') >>> d(55) ======================== RECORDING AND ASSERTIONS ======================== At any time we can get a list of calls that have been made to a dingus. Each entry in the call list contains: * the name of the method called (or "()" if the dingus itself was called) * The arguments, or () if none * The keyword argumnets, or {} if none * The value that was returned to the caller Here is a list of the calls we've made to d so far: >>> from pprint import pprint >>> pprint(d.calls) [('()', (), {}, ), ('()', ('argument',), {}, ), ('()', (55,), {}, )] You can filter calls by name, arguments, and keyword arguments: >>> pprint(d.calls('()', 55)) [('()', (55,), {}, )] If you don't care about a particular argument's value, you can use the value DontCare when filtering: >>> from dingus import DontCare >>> pprint(d.calls('()', DontCare)) [('()', ('argument',), {}, ), ('()', (55,), {}, )] Dinguses can do more than just have attributes accessed and be called. They support many Python operators. The goal is to allow, and record, any interaction: >>> d = Dingus('root') >>> (2 ** d.something)['hello']() / 100 * 'foo' (Hopefully your real-world dingus recordings won't look like this!) ======== PATCHING ======== Dingus provides a context manager for patching objects during tests. For example: >>> from dingus import patch >>> import urllib2 >>> with patch('urllib2.urlopen'): ... print urllib2.urlopen.__class__ >>> print urllib2.urlopen.__class__ You can also use this as a decorator on your test methods: >>> @patch('urllib2.urlopen') ... def test_something(self): ... pass ... ========= ISOLATION ========= The opposite of patch is isolate. It patches everything except the named object: >>> from dingus import isolate >>> @isolate('urllib2.urlparse') ... def test_urlparse(self): ... pass ... When this test runs, everything in the urllib2 module except urlparse will be a dingus. Note that this may be slow to execute if the module contains many objects; performance patches are welcome. :) =============== DANGEROUS MAGIC =============== Dingus can also automatically replace a module's globals when running tests. This allows you to write fully isolated unit tests. See examples/urllib2/test\_urllib2.py for an example. The author no longer recommends this feature, as it can encourage very brittle tests. You should feel the pain of manually mocking dependencies; the pain will tell you when a class collaborates with too many others. Keywords: testing test mocking mock double stub fake record assert Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Testing dingus-0.3.4/README.txt0000644000076500000240000000751411662607752014425 0ustar grbstaff00000000000000======== DINGUSES ======== A dingus is sort of like a mock object. The main difference is that you don't set up expectations ahead of time. You just run your code, using a dingus in place of another object or class, and it will record what happens to it. Then, once your code has been exercised, you can make assertions about what it did to the dingus. A new dingus is created from the Dingus class. You can give dinguses names, which helps with debugging your tests, especially when there are multiple dinguses in play. >>> from dingus import Dingus >>> d = Dingus('root') >>> d Accessing any attribute of a dingus will return a new dingus. >>> d.something There are a few exceptions for special dingus methods. We'll see some in a bit. A dingus can also be called like a function or method. It doesn't care how many arguments you give it or what those arguments are. Calls to a dingus will always return the same object, regardless of the arguments. >>> d() >>> d('argument') >>> d(55) ======================== RECORDING AND ASSERTIONS ======================== At any time we can get a list of calls that have been made to a dingus. Each entry in the call list contains: * the name of the method called (or "()" if the dingus itself was called) * The arguments, or () if none * The keyword argumnets, or {} if none * The value that was returned to the caller Here is a list of the calls we've made to d so far: >>> from pprint import pprint >>> pprint(d.calls) [('()', (), {}, ), ('()', ('argument',), {}, ), ('()', (55,), {}, )] You can filter calls by name, arguments, and keyword arguments: >>> pprint(d.calls('()', 55)) [('()', (55,), {}, )] If you don't care about a particular argument's value, you can use the value DontCare when filtering: >>> from dingus import DontCare >>> pprint(d.calls('()', DontCare)) [('()', ('argument',), {}, ), ('()', (55,), {}, )] Dinguses can do more than just have attributes accessed and be called. They support many Python operators. The goal is to allow, and record, any interaction: >>> d = Dingus('root') >>> (2 ** d.something)['hello']() / 100 * 'foo' (Hopefully your real-world dingus recordings won't look like this!) ======== PATCHING ======== Dingus provides a context manager for patching objects during tests. For example: >>> from dingus import patch >>> import urllib2 >>> with patch('urllib2.urlopen'): ... print urllib2.urlopen.__class__ >>> print urllib2.urlopen.__class__ You can also use this as a decorator on your test methods: >>> @patch('urllib2.urlopen') ... def test_something(self): ... pass ... ========= ISOLATION ========= The opposite of patch is isolate. It patches everything except the named object: >>> from dingus import isolate >>> @isolate('urllib2.urlparse') ... def test_urlparse(self): ... pass ... When this test runs, everything in the urllib2 module except urlparse will be a dingus. Note that this may be slow to execute if the module contains many objects; performance patches are welcome. :) =============== DANGEROUS MAGIC =============== Dingus can also automatically replace a module's globals when running tests. This allows you to write fully isolated unit tests. See examples/urllib2/test\_urllib2.py for an example. The author no longer recommends this feature, as it can encourage very brittle tests. You should feel the pain of manually mocking dependencies; the pain will tell you when a class collaborates with too many others. dingus-0.3.4/setup.cfg0000644000076500000240000000007311730161472014527 0ustar grbstaff00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 dingus-0.3.4/setup.py0000644000076500000240000000152211730161274014420 0ustar grbstaff00000000000000from setuptools import setup setup(name='dingus', version='0.3.4', description='A record-then-assert mocking library', long_description=file('README.txt').read(), author='Gary Bernhardt', author_email='gary.bernhardt@gmail.com', py_modules=['dingus'], data_files=[('', ['README.txt'])], license='MIT', url='https://github.com/garybernhardt/dingus', keywords='testing test mocking mock double stub fake record assert', classifiers=["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Software Development :: Testing", ], )