Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve unittest compatibility by allowing to add TestCases to Attest col #128

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion attest/collectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import inspect
import re
import sys
import types
import unittest

from contextlib import contextmanager
from functools import wraps
Expand All @@ -22,6 +24,44 @@
'TestBase',
]

class TestCaseProxy(object):
def __init__(self, testcase):
self.testcase = testcase

def _testcase_debug(self):
"""Run the test without collecting errors in a TestResult"""
self.setUp()
try:
getattr(self, self._testMethodName)()
finally:
self.tearDown()
while self._cleanups:
function, args, kwargs = self._cleanups.pop(-1)
function(*args, **kwargs)
self.testcase.debug = types.MethodType(_testcase_debug, self.testcase)

def __call__(self):
return self.testcase.debug()

def __eq__(self, rhs):
if not isinstance(rhs, TestCaseProxy):
return False
return self.testcase == rhs.testcase


def find_test_case_methods(test_class):
def is_test_method(attrname):
return attrname.startswith('test') \
and callable(getattr(test_class, attrname))
return sorted(filter(is_test_method, dir(test_class)))

def find_test_case_entries(test_class):
method_names = find_test_case_methods(test_class)
if method_names:
for method_name in method_names:
yield TestCaseProxy(test_class(method_name))
else:
yield TestCaseProxy(test_class())

class Tests(object):
"""Collection of test functions.
Expand Down Expand Up @@ -203,7 +243,15 @@ def register(self, tests):

"""
if inspect.isclass(tests):
self._tests.extend(tests())
if issubclass(tests, unittest.TestCase):
self.register(find_test_case_entries(tests))
else:
test = tests()
try:
iterable = iter(test)
except TypeError:
iterable = [test]
self.register(iterable)
return tests
elif isinstance(tests, basestring):
def istests(obj):
Expand All @@ -215,6 +263,8 @@ def istests(obj):
return
tests = obj
for test in tests:
if isinstance(test, unittest.TestCase):
test = TestCaseProxy(test)
if not test in self._tests:
self._tests.append(test)

Expand Down
134 changes: 133 additions & 1 deletion attest/tests/collectors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import with_statement
from attest import (AbstractReporter, Tests, TestBase, Assert, assert_hook,
test, TestFailure)
import unittest


class TestReporter(AbstractReporter):
Expand Down Expand Up @@ -177,7 +178,7 @@ def exclude():


@suite.test
def unittest():
def unittest_compat():
"""Compatibility with Python's unittest package"""
signals = set()

Expand Down Expand Up @@ -238,3 +239,134 @@ def test_something():
assert TestCase.test_something
assert TestCase.test_lambda

def unittest_wrap():
"""@Tests().unittest_wrap"""

col = Tests()

class UnitTestTestCase(unittest.TestCase):
def runTest(self):
pass
UnitTestTestCase = col.register(UnitTestTestCase)

assert len(col) == 1

@suite.test
def unittest_run_pass():
"""Wrapping passing TestCase"""

col = Tests()

class UnitTestTestCase(unittest.TestCase):
def runTest(self):
pass
UnitTestTestCase = col.register(UnitTestTestCase)

result = TestReporter()
col.run(result)
assert len(result.failed) == 0
assert len(result.succeeded) == 1

@suite.test
def unittest_run_fail_assert():
"""Wrapping TestCase failing with assert"""

col = Tests()

class UnitTestTestCase(unittest.TestCase):
def runTest(self):
assert 2 + 2 == 5
UnitTestTestCase = col.register(UnitTestTestCase)

result = TestReporter()
col.run(result)

assert len(result.failed) == 1
assert len(result.succeeded) == 0

@suite.test
def unittest_add_instance():
"""Wrapping TestCase instances"""

col = Tests()

class PassingUnitTestTestCase(unittest.TestCase):
def runTest(self):
pass
class FailingUnitTestTestCase(unittest.TestCase):
def runTest(self):
assert 2 + 2 == 5

col.register([PassingUnitTestTestCase(), FailingUnitTestTestCase()])

result = TestReporter()
col.run(result)

assert len(result.failed) == 1
assert len(result.succeeded) == 1

@suite.test
def unittest_check_setup_teardown():
"""Checking setUp/tearDown functioning"""

col = Tests()

class SetUpTearDownTestCase(unittest.TestCase):
def __init__(self):
self.set_up_invoked = False
self.tear_down_invoked = False
super(SetUpTearDownTestCase, self).__init__()

def setUp(self):
self.set_up_invoked = True
def runTest(self):
pass
def tearDown(self):
if not self.set_up_invoked:
raise RuntimeError()
self.tear_down_invoked = True

t = SetUpTearDownTestCase()
col.register([t])

result = TestReporter()
col.run(result)

assert len(result.succeeded) == 1
assert t.set_up_invoked == True
assert t.tear_down_invoked == True

@suite.test
def unittest_check_test_functions():
"""Checking test* functions"""

col = Tests()

class MultiTestCase(unittest.TestCase):
def setUp(self):
MultiTestCase.set_up_invoked += 1
def testFailOne(self):
assert False
def testFailTwo(self):
assert False
def testPassOne(self):
assert True
def testPassTwo(self):
assert True
def testPassThree(self):
assert True
def tearDown(self):
MultiTestCase.tear_down_invoked += 1

MultiTestCase.set_up_invoked = 0
MultiTestCase.tear_down_invoked = 0

col.register(MultiTestCase)

result = TestReporter()
col.run(result)

assert MultiTestCase.set_up_invoked == 5
assert MultiTestCase.tear_down_invoked == 5
assert len(result.succeeded) == 3
assert len(result.failed) == 2
1 change: 1 addition & 0 deletions docs/api/collectors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and separation.
* Lists of lambdas and function references.
* :class:`Tests` instances.
* Instances of subclasses of :class:`TestBase`.
* Instances of subclasses of :class:`unittest.TestCase`.
* Classes are instantiated and returned, allowing :meth:`Tests.register`
to be used as a class decorator in Python 2.6 or later.

Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

Attest is a unit testing framework built from the ground up with idiomatic
Python in mind. Unlike others, it is not built on top of unittest though it
provides compatibility by creating TestSuites from Attest collections.
provides compatibility by creating TestSuites from Attest collections and adding
TestCases to Attest collections.

It has a functional API inspired by `Flask`_ and a class-based API that
mimics Python itself. The core avoids complicated assumptions leaving you
Expand Down