Skip to content
Browse files

Initial commit

  • Loading branch information...
0 parents commit d4734ce10de501959ccbb0d53e286230225b27f4 @Fluxx committed
Showing with 281 additions and 0 deletions.
  1. +4 −0 .gitignore
  2. +25 −0 exam.sublime-project
  3. +4 −0 exam/__init__.py
  4. +27 −0 exam/cases.py
  5. +28 −0 exam/decorators.py
  6. +11 −0 exam/helpers.py
  7. +1 −0 exam/objects.py
  8. +45 −0 setup.py
  9. +63 −0 tests/test_cases.py
  10. +35 −0 tests/test_decorators.py
  11. +20 −0 tests/test_helpers.py
  12. +18 −0 tests/test_objects.py
4 .gitignore
@@ -0,0 +1,4 @@
+*.pyc
+.DS_Store
+*.egg
+*.egg-info
25 exam.sublime-project
@@ -0,0 +1,25 @@
+{
+ "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
+ }
+}
4 exam/__init__.py
@@ -0,0 +1,4 @@
+__version__ = '0.1.0'
+
+
+from decorators import fixture, before, after
27 exam/cases.py
@@ -0,0 +1,27 @@
+from decorators import before, after
+
+
+class Examify(type):
+
+ DECORATORS = dict(setUp=before, tearDown=after)
+
+ def __new__(cls, name, bases, attrs):
+ values = attrs.values()
+
+ for base in bases:
+ values.extend(vars(base).values())
+
+ for method, kind in cls.DECORATORS.items():
+ key = '%s_methods' % method
+ attrs.setdefault(key, [])
+ [attrs[key].append(v) for v in values if type(v) is kind]
+
+ return super(Examify, cls).__new__(cls, name, bases, attrs)
+
+
+class Exam(object):
+
+ __metaclass__ = Examify
+
+ setUp = lambda self: [method(self) for method in self.setUp_methods]
+ tearDown = lambda self: [method(self) for method in self.tearDown_methods]
28 exam/decorators.py
@@ -0,0 +1,28 @@
+class fixture(object):
+
+ def __init__(self, thing):
+ self.thing = thing
+
+ def __get__(self, obj, type=None):
+ if not self.thing in obj.__dict__:
+ obj.__dict__[self.thing] = self.thing(obj)
+
+ return obj.__dict__[self.thing]
+
+
+class before(object):
+
+ def __init__(self, thing):
+ self.thing = thing
+
+ def __call__(self, instance):
+ self.thing(instance)
+
+
+class after(object):
+
+ def __init__(self, thing):
+ self.thing = thing
+
+ def __call__(self, instance):
+ self.thing(instance)
11 exam/helpers.py
@@ -0,0 +1,11 @@
+import shutil
+import os
+
+
+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)
1 exam/objects.py
@@ -0,0 +1 @@
+no_op = lambda *a, **k: None
45 setup.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+import sys
+
+from setuptools import setup, find_packages
+
+from exam import __version__
+
+try:
+ import multiprocessing
+except ImportError:
+ pass
+
+install_requires = []
+lint_requires = ['pep8', 'pyflakes']
+tests_require = ['mock', 'nose', 'unittest2', 'describe==1.0.0beta1']
+
+dependency_links = [
+ 'https://github.com/jeffh/describe/tarball/dev#egg=describe'
+]
+
+setup_requires = []
+if 'nosetests' in sys.argv[1:]:
+ setup_requires.append('nose')
+
+setup(
+ name='exam',
+ version=__version__,
+ author='Jeff Pollard',
+ author_email='jeff.pollard@gmail.com',
+ url='https://github.com/fluxx/samsa',
+ 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',
+)
63 tests/test_cases.py
@@ -0,0 +1,63 @@
+from unittest2 import TestCase
+
+from exam import fixture, before, after
+from exam.cases import Exam
+
+from describe import expect
+
+
+class DummyTest(Exam):
+
+ def __init__(self):
+ self.calls = []
+
+ @before
+ def append_one(self):
+ print 'in append_one', self
+ self.calls.append(1)
+
+ @after
+ def append_two(self):
+ self.calls.append(2)
+
+
+class ExtendedDummy(DummyTest):
+
+ @before
+ def append_3(self):
+ self.calls.append(3)
+
+ @after
+ def append_4(self):
+ self.calls.append(4)
+
+
+class TestExam(TestCase):
+
+ @fixture
+ def case(self):
+ return DummyTest()
+
+ @fixture
+ def subclass_case(self):
+ return ExtendedDummy()
+
+ def test_before_adds_each_method_to_set_up(self):
+ expect(self.case.calls).to == []
+ self.case.setUp()
+ expect(self.case.calls).to == [1]
+
+ def test_after_adds_each_method_to_tear_down(self):
+ expect(self.case.calls).to == []
+ self.case.tearDown()
+ expect(self.case.calls).to == [2]
+
+ def test_before_works_on_subclasses(self):
+ expect(self.subclass_case.calls).to == []
+ self.subclass_case.setUp()
+ expect(self.subclass_case.calls).to == [3, 1]
+
+ def test_after_works_on_subclasses(self):
+ expect(self.subclass_case.calls).to == []
+ self.subclass_case.tearDown()
+ expect(self.subclass_case.calls).to == [4, 2]
35 tests/test_decorators.py
@@ -0,0 +1,35 @@
+from unittest2 import TestCase
+from describe import expect
+
+from exam import fixture
+
+
+class Dummy(object):
+
+ outside = 'value from outside'
+
+ inline = fixture(lambda self: self.outside)
+
+ @fixture
+ def number(self):
+ return 42
+
+ @fixture
+ def object(self):
+ return object()
+
+
+class TestFixture(TestCase):
+
+ def test_converts_method_to_property(self):
+ expect(Dummy().number).to == 42
+
+ def test_caches_property_on_same_instance(self):
+ instance = Dummy()
+ expect(instance.object).to.be_equal_to(instance.object)
+
+ def test_gives_different_object_on_separate_instances(self):
+ expect(Dummy().object).to_not.be_equal_to(Dummy().object)
+
+ def test_can_be_used_with_a_callable(self):
+ expect(Dummy().inline).to == 'value from outside'
20 tests/test_helpers.py
@@ -0,0 +1,20 @@
+from unittest2 import TestCase
+from mock import patch
+
+from exam.helpers import rm_f
+
+
+@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)
18 tests/test_objects.py
@@ -0,0 +1,18 @@
+from unittest2 import TestCase
+from describe import expect
+
+from exam.objects import no_op
+
+
+class TestNoOp(TestCase):
+
+ def test_can_be_called_with_anything(self):
+ no_op()
+ no_op(1)
+ no_op(key='val')
+ no_op(1, key='val')
+ no_op(1, 2, 3, key='val')
+ no_op(1, 2, 3, key='val', another='thing')
+
+ def test_returns_none(self):
+ expect(no_op()).to == None

0 comments on commit d4734ce

Please sign in to comment.
Something went wrong with that request. Please try again.