pax_global_header00006660000000000000000000000064123707635460014527gustar00rootroot0000000000000052 comment=41f323159461b8b170b383b1e76ee60926738e6b exam-0.10.5/000077500000000000000000000000001237076354600125445ustar00rootroot00000000000000exam-0.10.5/.gitignore000066400000000000000000000000731237076354600145340ustar00rootroot00000000000000*.pyc .DS_Store *.egg *.egg-info dist/ *.sublime-workspace exam-0.10.5/.travis.yml000066400000000000000000000002021237076354600146470ustar00rootroot00000000000000language: python python: - "2.6" - "2.7" - "3.2" - "3.3" - "pypy" install: python setup.py develop script: make test exam-0.10.5/Gemfile000066400000000000000000000002211237076354600140320ustar00rootroot00000000000000source :rubygems group :development do gem 'guard', '1.4.0' gem 'rb-readline' gem 'rb-fsevent', :require => false gem 'guard-shell' end exam-0.10.5/Gemfile.lock000066400000000000000000000005131237076354600147650ustar00rootroot00000000000000GEM remote: http://rubygems.org/ specs: guard (1.4.0) listen (>= 0.4.2) thor (>= 0.14.6) guard-shell (0.5.1) guard (>= 1.1.0) listen (0.5.3) rb-fsevent (0.9.2) rb-readline (0.4.2) thor (0.16.0) PLATFORMS ruby DEPENDENCIES guard (= 1.4.0) guard-shell rb-fsevent rb-readline exam-0.10.5/Guardfile000066400000000000000000000001641237076354600143720ustar00rootroot00000000000000guard :shell do watch(/^(tests|exam)(.*)\.py$/) do |match| puts `python setup.py nosetests` end end exam-0.10.5/LICENSE000066400000000000000000000020461237076354600135530ustar00rootroot00000000000000Copyright © 2012 Jeff Pollard Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.exam-0.10.5/MANIFEST.in000066400000000000000000000000321237076354600142750ustar00rootroot00000000000000recursive-exclude tests * exam-0.10.5/Makefile000066400000000000000000000003661237076354600142110ustar00rootroot00000000000000VERSION = $(shell python setup.py --version) test: python setup.py nosetests release: git tag $(VERSION) git push origin $(VERSION) git push origin master python setup.py sdist upload watch: bundle exec guard .PHONY: test release watch exam-0.10.5/README.rst000066400000000000000000000434151237076354600142420ustar00rootroot00000000000000.. image:: https://api.travis-ci.org/Fluxx/exam.png?branch=master :target: http://travis-ci.org/fluxx/exam #### Exam #### .. image:: https://dl.dropbox.com/u/3663715/exam.jpeg Exam is a Python toolkit for writing better tests. It aims to remove a lot of the boiler plate testing code one often writes, while still following Python conventions and adhering to the unit testing interface. Installation ------------ A simple ``pip install exam`` should do the trick. Rationale -------- Aside from the obvious "does the code work?", writings tests has many additional goals and benefits: 1. If written semantically, reading tests can help demostrate how the code is supposed to work to other developers. 2. If quick running, tests provide feedback during development that your changes are working or not having an adverse side effects. 3. If they're easy to write correctly, developers will write more tests and they will be of a higher quality. Unfortunately, the common pattern for writing Python unit tests tends to not offer any of these advantages. Often times results in ineffecient and unnessarily obtuse testing code. Additionally, common uses of the `mock` library can often result in repetitive boiler-plate code or ineffeciency during test runs. `exam` aims to improve the state of Python test writing by providing a toolkit of useful functionality to make writing quick, correct and useful tests and as painless as possible. Usage -------- Exam features a collection of useful modules: ``exam.decorators`` ~~~~~~~~~~~~~~~~~~~ Exam has some useful decorators to make your tests easier to write and understand. To utilize the ``@before``, ``@after``, ``@around`` and ``@patcher`` decorators, you must mixin the ``exam.cases.Exam`` class into your test case. It implements the appropriate ``setUp()`` and ``tearDown()`` methods necessary to make the decorators work. Note that the ``@fixture`` decorator works without needing to be defined inside of an Exam class. Still, it's a best practice to add the ``Exam`` mixin to your test cases. All of the decorators in ``exam.decorators``, as well as the ``Exam`` test case are available for import from the main ``exam`` package as well. I.e.: .. code:: python from exam import Exam from exam import fixture, before, after, around, patcher ``exam.decorators.fixture`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``@fixture`` decorator turns a method into a property (similar to the ``@property`` decorator, but also memoizes the return value). This lets you reference the property in your tests, i.e. ``self.grounds``, and it will always reference the exact same instance every time. .. code:: python from exam.decorators import fixture from exam.cases import Exam class MyTest(Exam, TestCase): @fixture def user(self): return User(name='jeff') def test_user_name_is_jeff(self): assert self.user.name == 'jeff' As you can see, ``self.user`` was used to reference the ``user`` property defined above. If all your fixture method is doing is contructing a new instance of type or calling a class method, exam provides a shorthand inline ``fixture`` syntax for constructing fixture objects. Simply set a class variable equal to ``fixture(type_or_class_method)`` and exam witll automatically call your type or class method. .. code:: python from exam.decorators import fixture from exam.cases import Exam class MyTest(Exam, TestCase): user = fixture(User, name='jeff') def test_user_name_is_jeff(self): assert self.user.name == 'jeff' Any ``*args`` or ``**kwargs`` passed to ``fixture(type_or_class_method)`` will be passed to the ``type_or_class_method`` when called. ``exam.decorators.before`` ^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``@before`` decorator adds the method to the list of methods which are run as part of the class's ``setUp()`` routine. .. code:: python from exam.decorators import before from exam.cases import Exam class MyTest(Exam, TestCase): @before def reset_database(self): mydb.reset() ``@before`` also hooks works through subclasses - that is to say, if a parent class has a ``@before`` hook in it, and you subclass it and define a 2nd ``@before`` hook in it, both ``@before`` hooks will be called. Exam runs the parent's ``@before`` hook first, then runs the childs'. Also, if your override a `@before` hook in your child class, the overriden method is run when the rest of the child classes `@before` hooks are run. For example, with hooks defined as such: .. code:: python from exam.decorators import before from exam.cases import Exam class MyTest(Exam, TestCase): @before def reset_database(self): print 'parent reset_db' @before def parent_hook(self): print 'parent hook' class RedisTest(MyTest): @before def reset_database(self): print 'child reset_db' @before def child_hook(self): print 'child hook' When Exam runs these hooks, the output would be: .. code:: python "prent hook" "child reset_db" "child hook" As you can see even though the parent class defines a ``reset_database``, because the child class overwrote it, the child's version is run intead, and also at the same time as the rest of the child's ``@before`` hooks. ``@before`` hooks can alse be constructed with other functions in your test case, decorating actual test methods. When this strategy is used, Exam will run the function ``@before`` is constructed with before running that particular test method. .. code:: python from exam.decorators import before, fixture from exam.cases import Exam from myapp import User class MyTest(Exam, TestCase): user = fixture(User) @before def create_user(self): self.user.create() def confirm_user(self): self.user.confirm() @before(confirm_user) def test_confirmed_users_have_no_token(self): self.assertFalse(self.user.token) def test_user_display_name_exists(self): self.assertTrue(self.user.display_name) In the above example, the ``confirm_user`` method is run immediatly before the ``test_confirmed_users_have_no_token`` method, but **not** the ``test_user_display_name_exists`` method. The ``@before`` globally decorated ``create_user`` method still runs before each test method. ``@before`` can also be constructed with multiple functions to call before running the test method: .. code:: python class MyTest(Exam, TestCase): @before(func1, func2) def test_does_things(self): does_things() In the above example, ``func1`` and ``func2`` are called in order before ``test_does_things`` is run. ``exam.decorators.after`` ^^^^^^^^^^^^^^^^^^^^^^^^^ The compliment to ``@before``, ``@after`` adds the method to the list of methods which are run as part of the class's ``tearDown()`` routine. Like ``@before``, ``@after`` runs parent class ``@after`` hooks before running ones defined in child classes. .. code:: python from exam.decorators import after from exam.cases import Exam class MyTest(Exam, TestCase): @after def remove_temp_files(self): myapp.remove_temp_files() ``exam.decorators.around`` ^^^^^^^^^^^^^^^^^^^^^^^^^ Methods decorated with ``@around`` act as a conxtext manager around each test method. In your around method, you're responsible for calling ``yield`` where you want the test case to run: .. code:: python from exam.decorators import around from exam.cases import Exam class MyTest(Exam, TestCase): @around def run_in_transaction(self): db.begin_transaction() yield # Run the test db.rollback_transaction() ``@around`` also follows the same parent/child ordering rules as ``@before`` and ``@after``, so parent ``@arounds`` will run (up until the ``yield`` statmement), then child ``@around``s will run. After the test method has finished, however, the rest of the child's ``@around`` will run, and then the parents's. This is done to preserve the normal behavior of nesting with context managers. ``exam.decorators.patcher`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``@patcher`` decorator is shorthand for the following boiler plate code: .. code:: python from mock import patch def setUp(self): self.stats_patcher = patch('mylib.stats', new=dummy_stats) self.stats = self.stats_patcher.start() def tearDown(self): self.stats_patcher.stop() Often, manually controlling a patch's start/stop is done to provide a test case property (here, ``self.stats``) for the mock object you are patching with. This is handy if you want the mock to have default behavior for most tests, but change it slightly for certain ones -- i.e absorb all calls most of the time, but for certain tests have it raise an exception. Using the ``@patcher`` decorator, the above code can simply be written as: .. code:: python from exam.decorators import patcher from exam.cases import Exam class MyTest(Exam, TestCase): @patcher('mylib.stats') def stats(self): return dummy_stats Exam takes care of starting and stopping the patcher appropriately, as well as constructing the ``patch`` object with the return value from the decorated method. If you're happy with the default constructed mock object for a patch (``MagicMock``), then ``patcher`` can simply be used as an inline as a function inside the class body. This method still starts and stops the patcher when needed, and returns the constructed ``MagicMock`` object, which you can set as a class attribute. Exam will add the ``MagicMock`` object to the test case as an instance attribute automatically. .. code:: python from exam.decorators import patcher from exam.cases import Exam class MyTest(Exam, TestCase): logger = patcher('coffee.logger') ``exam.decorators.patcher.object`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``patcher.object`` decorator provides the same features as the ``patcher`` decorator, but works with patching attributes of objects (similar to mock's ``mock.patch.object``). For example, here is how you would use patcher to patch the ``objects`` property of the ``User`` class: .. code:: python from exam.decorators import patcher from exam.cases import Exam from myapp import User class MyTest(Exam, TestCase): manager = patcher.object(User, 'objects') As with the vanilla ``patcher``, in your test case, ``self.manager`` will be the mock object that ``User.objects`` was patched with. ``exam.helpers`` ~~~~~~~~~~~~~~~~ The ``helpers`` module features a collection of helper methods for common testing patterns: ``exam.helpers.track`` ^^^^^^^^^^^^^^^^^^^^^^ The ``track`` helper is intended to assist in tracking call orders of independent mock objects. ``track`` is called with kwargs, where the key is the mock name (a string) and the value is the mock object you want to track. ``track`` returns a newly constructed ``MagicMock`` object, with each mock object attached at a attribute named after the mock name. For example, below ``track()`` creates a new mock with ``tracker.cool` as the ``cool_mock`` and ``tracker.heat`` as the ``heat_mock``. .. code:: python from exam.helpers import track @mock.patch('coffee.roast.heat') @mock.patch('coffee.roast.cool') def test_roasting_heats_then_cools_beans(self, cool_mock, heat_mock): tracker = track(heat=heat_mock, cool=cool_mock) roast.perform() tracker.assert_has_calls([mock.call.heat(), mock.call.cool()]) ``exam.helpers.rm_f`` ^^^^^^^^^^^^^^^^^^^^^ This is a simple helper that just removes all folders and files at a path: .. code:: python from exam.helpers import rm_f rm_f('/folder/i/do/not/care/about') ``exam.helpers.mock_import`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Removes most of the boiler plate code needed to mock imports, which usually consists of making a ``patch.dict`` from ``sys.modules``. Instead, the ``patch_import`` helper can simply be used as a decorator or context manager for when certain modules are imported. .. code:: python from exam.helpers import mock_import with mock_import('os.path') as my_os_path: import os.path as imported_os_path assert my_os_path is imported_os_path ``mock_import`` can also be used as a decorator, which passed the mock value to the testing method (like a normal ``@patch``) decorator: .. code:: python from exam.helpers import mock_import @mock_import('os.path') def test_method(self): import os.path as imported_os_path assert my_os_path is imported_os_path ``exam.helpers.effect`` ^^^^^^^^^^^^^^^^^^^^^^^ Helper class that is itself callable, whose return values when called are configured via the tuples passed in to the constructor. Useful to build ``side_effect`` callables for Mock objects. Raises TypeError if called with arguments that it was not configured with: >>> from exam.objects import call, effect >>> side_effect = effect((call(1), 'with 1'), (call(2), 'with 2')) >>> side_effect(1) 'with 1' >>> side_effect(2) 'with 2' Call argument equality is checked via equality (==) of the ``call``` object, which is the 0th item of the configuration tuple passed in to the ``effect`` constructor. By default, ``call`` objects are just ``mock.call`` objects. If you would like to customize this behavior, subclass `effect` and redefine your own `call_class` class variable. I.e. .. code:: python class myeffect(effect): call_class = my_call_class ``exam.mock`` ~~~~~~~~~~~~~ Exam has a subclass of the normal ``mock.Mock`` object that adds a few more useful methods to your mock objects. Use it in place of a normal ``Mock`` object: .. code:: python from exam.mock import Mock mock_user = Mock(spec=User) The subclass has the following extra methods: * ``assert_called()`` - Asserts the mock was called at least once. * ``assert_not_called()`` - Asserts the mock has never been called. * ``assert_not_called_with(*args, **kwargs)`` - Asserts the mock was not most recently called with the specified ``*args`` and ``**kwargs``. * ``assert_not_called_once_with(*args, **kwargs)`` - Asserts the mock has only every been called once with the specified ``*args`` and ``**kwargs``. * ``assert_not_any_call(*args, **kwargs)`` - Asserts the mock has never been called with the specified ``*args`` and ``**kwargs``. ``exam.fixtures`` ~~~~~~~~~~~~~~~~~ Helpful fixtures that you may want to use in your tests: * ``exam.fixtures.two_px_square_image`` - Image data as a string of a 2px square image. * ``exam.fixtures.one_px_spacer`` - Image data as a string of a 1px square spacer image. ``exam.objects`` ~~~~~~~~~~~~~~~~ Useful objects for use in testing: ``exam.objects.noop`` - callable object that always returns ``None``. no matter how it was called. ``exam.asserts`` ~~~~~~~~~~~~~~~~ The `asserts` module contains an `AssertsMixin` class, which is mixed into the main `Exam` test case mixin. It contains additional asserts beoynd the ones in Python's `unittest`. ``assertChanges`` ^^^^^^^^^^^^^^^^^ Used when you want to assert that a section of code changes a value. For example, imagine if you had a function that changed a soldier's rank. To properly test this, you should save that soldier's rank to a temporary variable, then run the function to change the rank, and then finally assert that the rank is the new expected value, as well as **not** the old value: .. code:: python test_changes_rank(self): old_rank = self.soldier.rank promote(self.soldier, 'general') self.assertEqual(self.soldier.rank, 'general') self.assertNotEqual(self.soldier.rank, old_rank) Checking the old rank is not the same is the new rank is important. If, for some reason there is a bug or something to where ``self.soldier`` is created with the rank of ``general``, but ``promote`` is not working, this test would still pass! To solve this, you can use Exam's ``assertChanges``: .. code:: python def test_changes_rank(self): with self.assertChanges(getattr, self.soldier, 'rank', after='general'): promote(self.soldier, 'general') This assert is doing a few things. 1. It asserts that the rank once the context is run is the expected ``general``. 2. It asserts that the context **changes** the value of ``self.soldier.rank``. 3. It doesn't actually care what the old value of ``self.soldier.rank`` was, as long as it changed when the context was run. The definition of ``assertChanges`` is: .. code:: python def assertChanges(thing, *args, **kwargs) 1. You pass it a ``thing``, which which be a callable. 2. ``assertChanges`` then calls your ``thing`` with any ``*args`` and ``**kwargs`` additionally passed in and captures the value as the "before" value. 3. The context is run, and then the callable is captured again as the "after" value. 4. If before and after are not different, an ``AssertionError`` is raised. 5. Additionally, if the special kwarg ``before`` or ``after`` are passed, those values are extracted and saved. In this case an ``AssertionError`` can also be raised if the "before" and/or "after" values provided do not match their extracted values. ``assertDoesNotChange`` ^^^^^^^^^^^^^^^^^^^^^^^ Similar to ``assertChanges``, ``assertDoesNotChange`` asserts that the code inside the context does not change the value from the callable: .. code:: python def test_does_not_change_rank(self): with self.assertDoesNotChange(getattr, self.soldier, 'rank'): self.soldier.march() Unlike ``assertChanges``, ``assertDoesNotChange`` does not take ``before`` or ``after`` kwargs. It simply asserts that the value of the callable did not change when the context was run. License ------- Exam is MIT licensed. Please see the ``LICENSE`` file for details. exam-0.10.5/exam.sublime-project000066400000000000000000000005731237076354600165310ustar00rootroot00000000000000{ "folders": [ { "folder_exclude_patterns": [ "*.egg", "build", "dist", "*.egg-info", "doc/_*", ".ropeproject" ], "file_exclude_patterns": [ "*.egg", "*.sublime-workspace", "*_pb2.py" ], "path": "." } ], "settings": { "tab_size": 4, "translate_tabs_to_spaces": true, "trim_trailing_white_space_on_save": true } } exam-0.10.5/exam/000077500000000000000000000000001237076354600134765ustar00rootroot00000000000000exam-0.10.5/exam/__init__.py000066400000000000000000000003031237076354600156030ustar00rootroot00000000000000from __future__ import absolute_import from exam.cases import Exam # NOQA from exam.helpers import intercept # NOQA from exam.decorators import before, after, around, fixture, patcher # NOQA exam-0.10.5/exam/asserts.py000066400000000000000000000040351237076354600155360ustar00rootroot00000000000000from functools import partial from operator import eq, ne IRRELEVANT = object() class ChangeWatcher(object): POSTCONDITION_FAILURE_MESSAGE = { ne: 'Value did not change', eq: 'Value changed from {before} to {after}', 'invalid': 'Value changed to {after}, not {expected_after}' } def __init__(self, comparator, check, *args, **kwargs): self.check = check self.comparator = comparator self.args = args self.kwargs = kwargs self.expected_before = kwargs.pop('before', IRRELEVANT) self.expected_after = kwargs.pop('after', IRRELEVANT) def __enter__(self): self.before = self.__apply() if not self.expected_before is IRRELEVANT: check = self.comparator(self.before, self.expected_before) message = "Value before is {before}, not {expected_before}" assert not check, message.format(**vars(self)) def __exit__(self, exec_type, exec_value, traceback): if exec_type is not None: return False # reraises original exception self.after = self.__apply() met_precondition = self.comparator(self.before, self.after) after_value_matches = self.after == self.expected_after # Changed when it wasn't supposed to, or, didn't change when it was if not met_precondition: self.__raise_postcondition_error(self.comparator) # Do care about the after value, but it wasn't equal elif self.expected_after is not IRRELEVANT and not after_value_matches: self.__raise_postcondition_error('invalid') def __apply(self): return self.check(*self.args, **self.kwargs) def __raise_postcondition_error(self, key): message = self.POSTCONDITION_FAILURE_MESSAGE[key] raise AssertionError(message.format(**vars(self))) class AssertsMixin(object): assertChanges = partial(ChangeWatcher, ne) assertDoesNotChange = partial( ChangeWatcher, eq, before=IRRELEVANT, after=IRRELEVANT ) exam-0.10.5/exam/cases.py000066400000000000000000000037261237076354600151560ustar00rootroot00000000000000from __future__ import absolute_import from exam.decorators import before, after, around, patcher # NOQA from exam.objects import noop # NOQA from exam.asserts import AssertsMixin import inspect class MultipleGeneratorsContextManager(object): def __init__(self, *generators): self.generators = generators def __enter__(self, *args, **kwargs): [next(g) for g in self.generators] def __exit__(self, *args, **kwargs): for generator in reversed(self.generators): try: next(generator) except StopIteration: pass class Exam(AssertsMixin): @before def __setup_patchers(self): for attr, patchr in self.__attrs_of_type(patcher): patch_object = patchr.build_patch(self) setattr(self, attr, patch_object.start()) self.addCleanup(patch_object.stop) def __attrs_of_type(self, kind): for base in reversed(inspect.getmro(type(self))): for attr, class_value in vars(base).items(): resolved_value = getattr(type(self), attr, False) if type(resolved_value) is not kind: continue # If the attribute inside of this base is not the exact same # value as the one in type(self), that means that it's been # overwritten somewhere down the line and we shall skip it elif class_value is not resolved_value: continue else: yield attr, resolved_value def __run_hooks(self, hook): for _, value in self.__attrs_of_type(hook): value(self) def run(self, *args, **kwargs): generators = (value(self) for _, value in self.__attrs_of_type(around)) with MultipleGeneratorsContextManager(*generators): self.__run_hooks(before) getattr(super(Exam, self), 'run', noop)(*args, **kwargs) self.__run_hooks(after) exam-0.10.5/exam/decorators.py000066400000000000000000000065471237076354600162310ustar00rootroot00000000000000from __future__ import absolute_import from mock import patch from functools import partial, wraps import types import exam.cases class fixture(object): def __init__(self, thing, *args, **kwargs): self.thing = thing self.args = args self.kwargs = kwargs def __get__(self, testcase, type=None): if not testcase: # Test case fixture was accesse as a class property, so just return # this fixture itself. return self elif self not in testcase.__dict__: # If this fixture is not present in the test case's __dict__, # freshly apply this fixture and store that in the dict, keyed by # self application = self.__apply(testcase)(*self.args, **self.kwargs) testcase.__dict__[self] = application return testcase.__dict__[self] def __apply(self, testcase): # If self.thing is a method type, it means that the function is already # bound to a class and therefore we should treat it just like a normal # functuion and return it. if type(self.thing) in (type, types.MethodType): return self.thing # If not, it means that's it's a vanilla function, # so either a decorated instance method in the test case # body or a lambda. In either of those # cases, it's called with the test case instance (self) to the author. else: return partial(self.thing, testcase) class base(object): def __init__(self, *things): self.init_callables = things def __call__(self, instance): return self.init_callables[0](instance) class before(base): def __call__(self, thing): # There a couple possible situations at this point: # # If ``thing`` is an instance of a test case, this means that we # ``init_callable`` is the function we want to decorate. As such, # simply call that callable with the instance. if isinstance(thing, exam.cases.Exam): return self.init_callables[0](thing) # If ``thing is not an instance of the test case, it means thi before # hook was constructed with a callable that we need to run before # actually running the decorated function. # It also means that ``thing`` is the function we're # decorating, so we need to return a callable that # accepts a test case instance and, when called, calls the # ``init_callable`` first, followed by the actual function we are # decorating. else: @wraps(thing) def inner(testcase): [f(testcase) for f in self.init_callables] thing(testcase) return inner class after(base): pass class around(base): pass class patcher(object): def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs self.func = None self.patch_func = patch def __call__(self, func): self.func = func return self def build_patch(self, instance): if self.func: self.kwargs['new'] = self.func(instance) return self.patch_func(*self.args, **self.kwargs) @classmethod def object(cls, *args, **kwargs): instance = cls(*args, **kwargs) instance.patch_func = patch.object return instance exam-0.10.5/exam/fixtures.py000066400000000000000000000012741237076354600157250ustar00rootroot00000000000000#: A string representation of a 2px square GIF, suitable for use in PIL. two_px_square_image = ( 'GIF87a\x02\x00\x02\x00\xb3\x00\x00\x00\x00\x00\xff\xff\xff\x00\x00' + '\x00\x00\x00\x00\x00\x00\x00\xff\x00\xff\x00\x00\x00\x00\x00\x00' + '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + '\x00\x00\x00\x00\x00\x00\x00\x00,\x00\x00\x00\x00\x02\x00\x02\x00' + '\x00\x04\x04\x10\x94\x02"\x00;' ) #: A string representation of a 1px square GIF, suitable for use in PIL. one_px_spacer = ( 'GIF89a\x01\x00\x01\x00\x80\x00\x00\xdb\xdf\xef\x00\x00\x00!\xf9\x04' + '\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D' + '\x01\x00;' ) exam-0.10.5/exam/helpers.py000066400000000000000000000106331237076354600155150ustar00rootroot00000000000000from __future__ import absolute_import import shutil import os import functools from mock import MagicMock, patch, call def rm_f(path): try: # Assume it's a directory shutil.rmtree(path, ignore_errors=True) except OSError: # Directory delete failed, so it's likely a file os.remove(path) def track(**mocks): tracker = MagicMock() for name, mocker in mocks.items(): tracker.attach_mock(mocker, name) return tracker def intercept(obj, methodname, wrapper): """ Wraps an existing method on an object with the provided generator, which will be "sent" the value when it yields control. :: >>> def ensure_primary_key_is_set(): ... assert model.pk is None ... saved = yield ... aasert model is saved ... assert model.pk is not None ... >>> intercept(model, 'save', ensure_primary_key_is_set) >>> model.save() :param obj: the object that has the method to be wrapped :type obj: :class:`object` :param methodname: the name of the method that will be wrapped :type methodname: :class:`str` :param wrapper: the wrapper :type wrapper: generator callable """ original = getattr(obj, methodname) def replacement(*args, **kwargs): wrapfn = wrapper(*args, **kwargs) wrapfn.send(None) result = original(*args, **kwargs) try: wrapfn.send(result) except StopIteration: return result else: raise AssertionError('Generator did not stop') def unwrap(): """ Restores the method to it's original (unwrapped) state. """ setattr(obj, methodname, original) replacement.unwrap = unwrap setattr(obj, methodname, replacement) class mock_import(patch.dict): FROM_X_GET_Y = lambda s, x, y: getattr(x, y) def __init__(self, path): self.mock = MagicMock() self.path = path self.modules = {self.base: self.mock} for i in range(len(self.remainder)): tail_parts = self.remainder[0:i + 1] key = '.'.join([self.base] + tail_parts) reduction = functools.reduce(self.FROM_X_GET_Y, tail_parts, self.mock) self.modules[key] = reduction super(mock_import, self).__init__('sys.modules', self.modules) @property def base(self): return self.path.split('.')[0] @property def remainder(self): return self.path.split('.')[1:] def __enter__(self): super(mock_import, self).__enter__() return self.modules[self.path] def __call__(self, func): super(mock_import, self).__call__(func) @functools.wraps(func) def inner(*args, **kwargs): args = list(args) args.insert(1, self.modules[self.path]) with self: func(*args, **kwargs) return inner class effect(list): """ Helper class that is itself callable, whose return values when called are configured via the tuples passed in to the constructor. Useful to build ``side_effect`` callables for Mock objects. Raises TypeError if called with arguments that it was not configured with: >>> from exam.objects import call, effect >>> side_effect = effect((call(1), 'with 1'), (call(2), 'with 2')) >>> side_effect(1) 'with 1' >>> side_effect(2) 'with 2' Call argument equality is checked via equality (==) of the ``call``` object, which is the 0th item of the configuration tuple passed in to the ``effect`` constructor. By default, ``call`` objects are just ``mock.call`` objects. If you would like to customize this behavior, subclass `effect` and redefine your own `call_class` class variable. I.e. class myeffect(effect): call_class = my_call_class """ call_class = call def __init__(self, *calls): """ :param calls: Two-item tuple containing call and the return value. :type calls: :class:`effect.call_class` """ super(effect, self).__init__(calls) def __call__(self, *args, **kwargs): this_call = self.call_class(*args, **kwargs) for call_obj, return_value in self: if call_obj == this_call: return return_value raise TypeError('Unknown effect for: %r, %r' % (args, kwargs)) exam-0.10.5/exam/mock.py000066400000000000000000000014301237076354600147770ustar00rootroot00000000000000from __future__ import absolute_import from mock import Mock as BaseMock from mock import call class Mock(BaseMock): def assert_called(self): assert self.called def assert_not_called(self): assert not self.called def assert_not_called_with(self, *args, **kwargs): assert not call(*args, **kwargs) == self.call_args def assert_not_called_once_with(self, *args, **kwargs): assert len(self.__calls_matching(*args, **kwargs)) is not 1 def assert_not_any_call(self, *args, **kwargs): assert len(self.__calls_matching(*args, **kwargs)) is 0 def __calls_matching(self, *args, **kwargs): calls_match = lambda other_call: call(*args, **kwargs) == other_call return list(filter(calls_match, self.call_args_list)) exam-0.10.5/exam/objects.py000066400000000000000000000001721237076354600155010ustar00rootroot00000000000000from __future__ import absolute_import def always(value): return lambda *a, **k: value noop = no_op = always(None) exam-0.10.5/setup.py000077500000000000000000000025061237076354600142640ustar00rootroot00000000000000#!/usr/bin/env python import sys from setuptools import setup, find_packages try: import multiprocessing # NOQA except ImportError: pass install_requires = ['mock'] lint_requires = ['pep8', 'pyflakes'] tests_require = ['nose'] if sys.version_info < (2, 7): tests_require.append('unittest2') setup_requires = [] if 'nosetests' in sys.argv[1:]: setup_requires.append('nose') setup( name='exam', version='0.10.5', author='Jeff Pollard', author_email='jeff.pollard@gmail.com', url='https://github.com/fluxx/exam', description='Helpers for better testing.', license='MIT', packages=find_packages(), install_requires=install_requires, tests_require=tests_require, setup_requires=setup_requires, extras_require={ 'test': tests_require, 'all': install_requires + tests_require, 'docs': ['sphinx'] + tests_require, 'lint': lint_requires }, zip_safe=False, test_suite='nose.collector', classifiers=[ "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", ], ) exam-0.10.5/tests/000077500000000000000000000000001237076354600137065ustar00rootroot00000000000000exam-0.10.5/tests/__init__.py000066400000000000000000000002061237076354600160150ustar00rootroot00000000000000import sys if sys.version_info < (2, 7): from unittest2 import TestCase # NOQA else: from unittest import TestCase # NOQA exam-0.10.5/tests/dummy.py000066400000000000000000000004131237076354600154110ustar00rootroot00000000000000#: Module purely exists to test patching things. thing = True it = lambda: False def get_thing(): global thing return thing def get_it(): global it return it def get_prop(): return ThingClass.prop class ThingClass(object): prop = True exam-0.10.5/tests/test_asserts.py000066400000000000000000000041771237076354600170140ustar00rootroot00000000000000from tests import TestCase from exam import Exam, fixture from exam.asserts import AssertsMixin class AssertChangesMixin(Exam, TestCase): case = fixture(AssertsMixin) thing = fixture(list) def no_op_context(self, *args, **kwargs): with self.case.assertChanges(len, self.thing, *args, **kwargs): pass def test_checks_change_on_callable_passed(self): with self.case.assertChanges(len, self.thing, before=0, after=1): self.thing.append(1) def test_after_check_asserts_ends_on_after_value(self): self.thing.append(1) with self.case.assertChanges(len, self.thing, after=2): self.thing.append(1) def test_before_check_asserts_starts_on_before_value(self): self.thing.append(1) with self.case.assertChanges(len, self.thing, before=1): self.thing.append(1) self.thing.append(2) def test_verifies_value_must_change_no_matter_what(self): self.thing.append(1) with self.assertRaises(AssertionError): self.no_op_context(after=1) with self.assertRaises(AssertionError): self.no_op_context(before=1) with self.assertRaises(AssertionError): self.no_op_context() def test_reraises_exception_if_raised_in_context(self): with self.assertRaises(NameError): with self.case.assertChanges(len, self.thing, after=5): self.thing.append(1) undefined_name def test_does_not_change_passes_if_no_change_was_made(self): with self.assertDoesNotChange(len, self.thing): pass def test_raises_assertion_error_if_value_changes(self): msg = 'Value changed from 0 to 1' with self.assertRaisesRegexp(AssertionError, msg): with self.assertDoesNotChange(len, self.thing): self.thing.append(1) def test_assertion_error_mentions_unexpected_result_at_after(self): msg = 'Value changed to 1, not 3' with self.assertRaisesRegexp(AssertionError, msg): with self.assertChanges(len, self.thing, after=3): self.thing.append(1) exam-0.10.5/tests/test_cases.py000066400000000000000000000173641237076354600164300ustar00rootroot00000000000000from mock import sentinel from tests import TestCase from exam.decorators import before, after, around, patcher from exam.cases import Exam from tests.dummy import get_thing, get_it, get_prop, ThingClass class SimpleTestCase(object): """ Meant to act like a typical unittest.TestCase """ def __init__(self): self.cleanups = [] self.setups = 0 self.teardowns = 0 def setUp(self): self.setups += 1 def tearDown(self): self.teardowns += 1 def run(self, *args, **kwargs): # At this point in time, exam has run its before hooks and has super'd # to the TestCase (us), so, capture the state of calls self.calls_before_run = list(self.calls) self.vars_when_run = vars(self) def addCleanup(self, func): self.cleanups.append(func) class BaseTestCase(Exam, SimpleTestCase): """ Meant to act like a test case a typical user would have. """ def __init__(self, *args, **kwargs): self.calls = [] super(BaseTestCase, self).__init__(*args, **kwargs) def setUp(self): """ Exists only to prove that adding a setUp method to a test case does not break Exam. """ pass def tearDown(self): """ Exists only to prove that adding a tearDown method to a test case does not break Exam. """ pass class CaseWithBeforeHook(BaseTestCase): @before def run_before(self): self.calls.append('run before') class CaseWithDecoratedBeforeHook(BaseTestCase): def setup_some_state(self): self.state = True def setup_some_other_state(self): self.other_state = True @before(setup_some_state) def should_have_run_before(self): pass @before(setup_some_state, setup_some_other_state) def should_have_run_both_states(self): pass class SubclassWithBeforeHook(CaseWithBeforeHook): @before def subclass_run_before(self): self.calls.append('subclass run before') class CaseWithAfterHook(CaseWithBeforeHook): @after def run_after(self): self.calls.append('run after') class SubclassCaseWithAfterHook(CaseWithAfterHook): @after def subclass_run_after(self): self.calls.append('subclass run after') class CaseWithAroundHook(BaseTestCase): @around def run_around(self): self.calls.append('run around before') yield self.calls.append('run around after') class SubclassCaseWithAroundHook(BaseTestCase): @around def subclass_run_around(self): self.calls.append('subclass run around before') yield self.calls.append('subclass run around after') class CaseWithPatcher(BaseTestCase): @patcher('tests.dummy.thing') def dummy_thing(self): return sentinel.mock dummy_it = patcher('tests.dummy.it', return_value=12) class SubclassedCaseWithPatcher(CaseWithPatcher): pass class CaseWithPatcherObject(BaseTestCase): @patcher.object(ThingClass, 'prop') def dummy_thing(self): return 15 class SubclassedCaseWithPatcherObject(CaseWithPatcherObject): pass # TODO: Make the subclass checking just be a subclass of the test case class TestExam(Exam, TestCase): def test_assert_changes_is_asserts_mixin_assert_changes(self): from exam.asserts import AssertsMixin self.assertEqual(AssertsMixin.assertChanges, Exam.assertChanges) def test_before_runs_method_before_test_case(self): case = CaseWithBeforeHook() self.assertEqual(case.calls, []) case.run() self.assertEqual(case.calls_before_run, ['run before']) def test_before_decorator_runs_func_before_function(self): case = CaseWithDecoratedBeforeHook() self.assertFalse(hasattr(case, 'state')) case.should_have_run_before() self.assertTrue(case.state) def test_before_decorator_runs_multiple_funcs(self): case = CaseWithDecoratedBeforeHook() self.assertFalse(hasattr(case, 'state')) self.assertFalse(hasattr(case, 'other_state')) case.should_have_run_both_states() self.assertTrue(case.state) self.assertTrue(case.other_state) def test_before_decorator_does_not_squash_func_name(self): self.assertEqual( CaseWithDecoratedBeforeHook.should_have_run_before.__name__, 'should_have_run_before' ) def test_after_adds_each_method_after_test_case(self): case = CaseWithAfterHook() self.assertEqual(case.calls, []) case.run() self.assertEqual(case.calls, ['run before', 'run after']) def test_around_calls_methods_before_and_after_run(self): case = CaseWithAroundHook() self.assertEqual(case.calls, []) case.run() self.assertEqual(case.calls_before_run, ['run around before']) self.assertEqual(case.calls, ['run around before', 'run around after']) def test_before_works_on_subclasses(self): case = SubclassWithBeforeHook() self.assertEqual(case.calls, []) case.run() # The only concern with ordering here is that the parent class's # @before hook fired before it's parents. The actual order of the # @before hooks within a level of class is irrelevant. self.assertEqual(case.calls, ['run before', 'subclass run before']) def test_after_works_on_subclasses(self): case = SubclassCaseWithAfterHook() self.assertEqual(case.calls, []) case.run() self.assertEqual(case.calls_before_run, ['run before']) self.assertEqual(case.calls, ['run before', 'run after', 'subclass run after']) def test_around_works_with_subclasses(self): case = SubclassCaseWithAroundHook() self.assertEqual(case.calls, []) case.run() self.assertEqual(case.calls_before_run, ['subclass run around before']) self.assertEqual(case.calls, ['subclass run around before', 'subclass run around after']) def test_patcher_start_value_is_added_to_case_dict_on_run(self): case = CaseWithPatcher() case.run() self.assertEqual(case.vars_when_run['dummy_thing'], sentinel.mock) def test_patcher_patches_object_on_setup_and_adds_patcher_to_cleanup(self): case = CaseWithPatcher() self.assertNotEqual(get_thing(), sentinel.mock) case.run() self.assertEqual(get_thing(), sentinel.mock) [cleanup() for cleanup in case.cleanups] self.assertNotEqual(get_thing(), sentinel.mock) def test_patcher_lifecycle_works_on_subclasses(self): case = SubclassedCaseWithPatcher() self.assertNotEqual(get_thing(), sentinel.mock) case.run() self.assertEqual(get_thing(), sentinel.mock) [cleanup() for cleanup in case.cleanups] self.assertNotEqual(get_thing(), sentinel.mock) def test_patcher_patches_with_a_magic_mock_if_no_function_decorated(self): case = CaseWithPatcher() self.assertNotEqual(get_it()(), 12) case.run() self.assertEqual(get_it()(), 12) case.cleanups[0]() self.assertNotEqual(get_thing(), 12) def test_patcher_object_patches_object(self): case = CaseWithPatcherObject() self.assertNotEqual(get_prop(), 15) case.run() self.assertEqual(get_prop(), 15) [cleanup() for cleanup in case.cleanups] self.assertNotEqual(get_prop(), 15) def test_patcher_object_works_with_subclasses(self): case = SubclassedCaseWithPatcherObject() self.assertNotEqual(get_prop(), 15) case.run() self.assertEqual(get_prop(), 15) [cleanup() for cleanup in case.cleanups] self.assertNotEqual(get_prop(), 15) exam-0.10.5/tests/test_decorators.py000066400000000000000000000042511237076354600174660ustar00rootroot00000000000000from tests import TestCase from exam.decorators import fixture class Outer(object): @classmethod def meth(cls): return cls, 'from method' @classmethod def reflective_meth(cls, arg): return cls, arg class Dummy(object): outside = 'value from outside' @fixture def number(self): return 42 @fixture def obj(self): return object() inline = fixture(int, 5) inline_func = fixture(lambda self: self.outside) inline_func_with_args = fixture(lambda *a, **k: (a, k), 1, 2, a=3) inline_from_method = fixture(Outer.meth) inline_from_method_with_arg_1 = fixture(Outer.reflective_meth, 1) inline_from_method_with_arg_2 = fixture(Outer.reflective_meth, 2) class ExtendedDummy(Dummy): @fixture def number(self): return 42 + 42 class TestFixture(TestCase): def test_converts_method_to_property(self): self.assertEqual(Dummy().number, 42) def test_caches_property_on_same_instance(self): instance = Dummy() self.assertEqual(instance.obj, instance.obj) def test_gives_different_object_on_separate_instances(self): self.assertNotEqual(Dummy().obj, Dummy().obj) def test_can_be_used_inline_with_a_function(self): self.assertEqual(Dummy().inline_func, 'value from outside') def test_can_be_used_with_a_callable_that_takes_args(self): inst = Dummy() self.assertEqual(inst.inline_func_with_args, ((inst, 1, 2), dict(a=3))) def test_can_be_used_with_class_method(self): self.assertEqual(Dummy().inline_from_method, (Outer, 'from method')) def test_if_passed_type_builds_new_object(self): self.assertEqual(Dummy().inline, 5) def test_override_in_subclass_overrides_value(self): self.assertEqual(ExtendedDummy().number, 42 + 42) def test_captures_identical_funcs_with_args_separatly(self): instance = Dummy() first = instance.inline_from_method_with_arg_1 second = instance.inline_from_method_with_arg_2 self.assertNotEqual(first, second) def test_clas_access_returns_fixture_itself(self): self.assertEqual(getattr(Dummy, 'number'), Dummy.number) exam-0.10.5/tests/test_exam.py000066400000000000000000000010501237076354600162450ustar00rootroot00000000000000from tests import TestCase import exam class TestExam(TestCase): DECORATORS = ('fixture', 'before', 'after', 'around', 'patcher') def test_exam_is_cases_exam(self): from exam.cases import Exam self.assertEqual(exam.Exam, Exam) def test_imports_all_the_decorators(self): import exam.decorators for decorator in self.DECORATORS: from_decorators = getattr(exam.decorators, decorator) from_root = getattr(exam, decorator) self.assertEqual(from_root, from_decorators) exam-0.10.5/tests/test_helpers.py000066400000000000000000000067751237076354600170000ustar00rootroot00000000000000from tests import TestCase from mock import patch, Mock, sentinel from exam.helpers import intercept, rm_f, track, mock_import, call, effect from exam.decorators import fixture @patch('exam.helpers.shutil') class TestRmrf(TestCase): path = '/path/to/folder' def test_calls_shutil_rmtreee(self, shutil): rm_f(self.path) shutil.rmtree.assert_called_once_with(self.path, ignore_errors=True) @patch('exam.helpers.os') def test_on_os_errors_calls_os_remove(self, os, shutil): shutil.rmtree.side_effect = OSError rm_f(self.path) os.remove.assert_called_once_with(self.path) class TestTrack(TestCase): @fixture def foo_mock(self): return Mock() @fixture def bar_mock(self): return Mock() def test_makes_new_mock_and_attaches_each_kwarg_to_it(self): tracker = track(foo=self.foo_mock, bar=self.bar_mock) self.assertEqual(tracker.foo, self.foo_mock) self.assertEqual(tracker.bar, self.bar_mock) class TestMockImport(TestCase): def test_is_a_context_manager_that_yields_patched_import(self): with mock_import('foo') as mock_foo: import foo self.assertEqual(foo, mock_foo) def test_mocks_import_for_packages(self): with mock_import('foo.bar.baz') as mock_baz: import foo.bar.baz self.assertEqual(foo.bar.baz, mock_baz) @mock_import('foo') def test_can_be_used_as_a_decorator_too(self, mock_foo): import foo self.assertEqual(foo, mock_foo) @mock_import('foo') @mock_import('bar') def test_stacked_adds_args_bottom_up(self, mock_bar, mock_foo): import foo import bar self.assertEqual(mock_bar, bar) self.assertEqual(mock_foo, foo) class TestIntercept(TestCase): class Example(object): def method(self, positional, keyword): return sentinel.METHOD_RESULT def test_intercept(self): ex = self.Example() def counter(positional, keyword): assert positional is sentinel.POSITIONAL_ARGUMENT assert keyword is sentinel.KEYWORD_ARGUMENT result = yield assert result is sentinel.METHOD_RESULT counter.calls += 1 counter.calls = 0 intercept(ex, 'method', counter) self.assertEqual(counter.calls, 0) assert ex.method( sentinel.POSITIONAL_ARGUMENT, keyword=sentinel.KEYWORD_ARGUMENT) is sentinel.METHOD_RESULT self.assertEqual(counter.calls, 1) ex.method.unwrap() assert ex.method( sentinel.POSITIONAL_ARGUMENT, keyword=sentinel.KEYWORD_ARGUMENT) is sentinel.METHOD_RESULT self.assertEqual(counter.calls, 1) class TestEffect(TestCase): def test_creates_callable_mapped_to_config_dict(self): config = [ (call(1), 2), (call('a'), 3), (call(1, b=2), 4), (call(c=3), 5) ] side_effecet = effect(*config) self.assertEqual(side_effecet(1), 2) self.assertEqual(side_effecet('a'), 3) self.assertEqual(side_effecet(1, b=2), 4) self.assertEqual(side_effecet(c=3), 5) def test_raises_type_error_when_called_with_unknown_args(self): side_effect = effect((call(1), 5)) self.assertRaises(TypeError, side_effect, 'junk') def test_can_be_used_with_mutable_data_structs(self): side_effect = effect((call([1, 2, 3]), 'list')) self.assertEqual(side_effect([1, 2, 3]), 'list') exam-0.10.5/tests/test_mock.py000066400000000000000000000042041237076354600162500ustar00rootroot00000000000000from tests import TestCase from exam.mock import Mock from exam.decorators import fixture, before class MockTest(TestCase): mock = fixture(Mock) @before def assert_mock_clean(self): self.mock.assert_not_called() def test_assert_called_asserts_mock_was_called(self): self.assertRaises(AssertionError, self.mock.assert_called) self.mock() self.mock.assert_called() self.mock.reset_mock() self.assertRaises(AssertionError, self.mock.assert_called) def test_assert_not_called_asserts_mock_was_not_called(self): self.mock() self.assertRaises(AssertionError, self.mock.assert_not_called) self.mock.reset_mock() self.mock.assert_not_called() def test_assert_not_called_with_asserts_not_called_with_args(self): self.mock(1, 2, three=4) self.mock.assert_called_with(1, 2, three=4) self.mock.assert_not_called_with(1, 2, four=5) self.mock.assert_not_called_with(1, three=5) self.mock.assert_not_called_with() with self.assertRaises(AssertionError): self.mock.assert_not_called_with(1, 2, three=4) self.mock('foo') self.mock.assert_not_called_with(1, 2, three=4) # not the latest call def test_assert_not_called_once_with_asserts_one_call_with_args(self): self.mock.assert_not_called_once_with(1, 2, three=4) # 0 times self.mock(1, 2, three=4) with self.assertRaises(AssertionError): self.mock.assert_not_called_once_with(1, 2, three=4) # 1 time self.mock(1, 2, three=4) self.mock.assert_not_called_once_with(1, 2, three=4) # 2 times def test_assert_not_any_call_asserts_never_called_with_args(self): self.mock.assert_not_any_call(1, 2, three=4) self.mock(1, 2, three=4) with self.assertRaises(AssertionError): self.mock.assert_not_any_call(1, 2, three=4) self.mock('foo') with self.assertRaises(AssertionError): # Even though it's not the latest, it was previously called with # these args self.mock.assert_not_any_call(1, 2, three=4) exam-0.10.5/tests/test_objects.py000066400000000000000000000011241237076354600167460ustar00rootroot00000000000000from mock import sentinel from tests import TestCase from exam.objects import always, noop class TestAlways(TestCase): def test_always_returns_identity(self): fn = always(sentinel.RESULT_VALUE) assert fn() is sentinel.RESULT_VALUE assert fn(1, key='value') is sentinel.RESULT_VALUE def test_can_be_called_with_anything(self): noop() noop(1) noop(key='val') noop(1, key='val') noop(1, 2, 3, key='val') noop(1, 2, 3, key='val', another='thing') def test_returns_none(self): self.assertIsNone(noop())