summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZane Bitter <zbitter@redhat.com>2018-04-18 15:06:13 -0400
committerZane Bitter <zbitter@redhat.com>2018-11-19 16:41:38 -0500
commitde9830ec7d87bccbd99920cbcfc9008f9b4d951c (patch)
tree5c5d89f7984c329d37beb8976c87a316a2d2f1f8
parent7f9daba0ec01e494d8a283cb23e9e531343244b3 (diff)
downloadtesttools-de9830ec7d87bccbd99920cbcfc9008f9b4d951c.tar.gz
Allow skip/skipIf/skipUnless as class decorators
In unittest/unittest2 the skip/skipIf/skipUnless decorators can be used to decorate either individual test methods or entire TestCase classes. However, the testtools equivalents could previously only be used to decorate test methods. If used on a TestCase the class would be replaced with a function, resulting in the tests either not be discovered at all or (if a custom test loader was used) potentially an error. This change allows the skip decorators to be used on the TestCase subclass, for equivalent functionality with unittest. Fixes #205 Signed-off-by: Zane Bitter <zbitter@redhat.com>
-rw-r--r--NEWS10
-rw-r--r--README.rst1
-rw-r--r--testtools/runtest.py6
-rw-r--r--testtools/testcase.py16
-rw-r--r--testtools/tests/test_testcase.py65
5 files changed, 92 insertions, 6 deletions
diff --git a/NEWS b/NEWS
index bf76c96..fd2a2b4 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,16 @@ testtools NEWS
Changes and improvements to testtools_, grouped by release.
+NEXT
+~~~~
+
+Improvements
+------------
+
+* The skip, skipIf, and skipUnless decorators can now be used as class
+ decorators as well as test method decorators, just as they can in
+ unittest.
+
2.3.0
~~~~~
diff --git a/README.rst b/README.rst
index e46faef..6a9150f 100644
--- a/README.rst
+++ b/README.rst
@@ -93,3 +93,4 @@ Thanks
* ClusterHQ Ltd
* Tristan Seligmann
* Jonathan Jacobs
+ * Zane Bitter
diff --git a/testtools/runtest.py b/testtools/runtest.py
index cb38d78..eecf072 100644
--- a/testtools/runtest.py
+++ b/testtools/runtest.py
@@ -124,10 +124,12 @@ class RunTest(object):
def _run_core(self):
"""Run the user supplied test code."""
test_method = self.case._get_test_method()
- if getattr(test_method, '__unittest_skip__', False):
+ skip_case = getattr(self.case, '__unittest_skip__', False)
+ if skip_case or getattr(test_method, '__unittest_skip__', False):
self.result.addSkip(
self.case,
- reason=getattr(test_method, '__unittest_skip_why__', None)
+ reason=getattr(self.case if skip_case else test_method,
+ '__unittest_skip_why__', None)
)
return
diff --git a/testtools/testcase.py b/testtools/testcase.py
index 9fce671..2e128f2 100644
--- a/testtools/testcase.py
+++ b/testtools/testcase.py
@@ -20,6 +20,7 @@ import copy
import functools
import itertools
import sys
+import types
import warnings
from extras import (
@@ -940,6 +941,12 @@ class WithAttributes(object):
return orig + '[' + ','.join(sorted(attributes)) + ']'
+class_types = [type]
+if getattr(types, 'ClassType', None) is not None:
+ class_types.append(types.ClassType)
+class_types = tuple(class_types)
+
+
def skip(reason):
"""A decorator to skip unit tests.
@@ -948,10 +955,11 @@ def skip(reason):
@unittest.skip decorator.
"""
def decorator(test_item):
- @functools.wraps(test_item)
- def skip_wrapper(*args, **kwargs):
- raise TestCase.skipException(reason)
- test_item = skip_wrapper
+ if not isinstance(test_item, class_types):
+ @functools.wraps(test_item)
+ def skip_wrapper(*args, **kwargs):
+ raise TestCase.skipException(reason)
+ test_item = skip_wrapper
# This attribute signals to RunTest._run_core that the entire test
# must be skipped - including setUp and tearDown. This makes us
diff --git a/testtools/tests/test_testcase.py b/testtools/tests/test_testcase.py
index 7076179..56a8152 100644
--- a/testtools/tests/test_testcase.py
+++ b/testtools/tests/test_testcase.py
@@ -43,6 +43,7 @@ from testtools.testcase import (
attr,
Nullary,
WithAttributes,
+ TestSkipped,
)
from testtools.testresult.doubles import (
Python26TestResult,
@@ -1608,6 +1609,48 @@ class TestSkipping(TestCase):
test2.run(result2)
self.assertEqual('addFailure', events2[1][0])
+ def test_skip_class_decorator(self):
+ @skip("skipping this testcase")
+ class SkippingTest(TestCase):
+ def test_that_is_decorated_with_skip(self):
+ self.fail()
+ events = []
+ result = Python26TestResult(events)
+ try:
+ test = SkippingTest("test_that_is_decorated_with_skip")
+ except TestSkipped:
+ self.fail('TestSkipped raised')
+ test.run(result)
+ self.assertEqual('addSuccess', events[1][0])
+
+ def test_skipIf_class_decorator(self):
+ @skipIf(True, "skipping this testcase")
+ class SkippingTest(TestCase):
+ def test_that_is_decorated_with_skipIf(self):
+ self.fail()
+ events = []
+ result = Python26TestResult(events)
+ try:
+ test = SkippingTest("test_that_is_decorated_with_skipIf")
+ except TestSkipped:
+ self.fail('TestSkipped raised')
+ test.run(result)
+ self.assertEqual('addSuccess', events[1][0])
+
+ def test_skipUnless_class_decorator(self):
+ @skipUnless(False, "skipping this testcase")
+ class SkippingTest(TestCase):
+ def test_that_is_decorated_with_skipUnless(self):
+ self.fail()
+ events = []
+ result = Python26TestResult(events)
+ try:
+ test = SkippingTest("test_that_is_decorated_with_skipUnless")
+ except TestSkipped:
+ self.fail('TestSkipped raised')
+ test.run(result)
+ self.assertEqual('addSuccess', events[1][0])
+
def check_skip_decorator_does_not_run_setup(self, decorator, reason):
class SkippingTest(TestCase):
@@ -1623,6 +1666,28 @@ class TestSkipping(TestCase):
self.fail()
test = SkippingTest('test_skipped')
+ self.check_test_does_not_run_setup(test, reason)
+
+ # Use the decorator passed to us:
+ @decorator
+ class SkippingTestCase(TestCase):
+
+ setup_ran = False
+
+ def setUp(self):
+ super(SkippingTestCase, self).setUp()
+ self.setup_ran = True
+
+ def test_skipped(self):
+ self.fail()
+
+ try:
+ test = SkippingTestCase('test_skipped')
+ except TestSkipped:
+ self.fail('TestSkipped raised')
+ self.check_test_does_not_run_setup(test, reason)
+
+ def check_test_does_not_run_setup(self, test, reason):
result = test.run()
self.assertTrue(result.wasSuccessful())
self.assertTrue(reason in result.skip_reasons, result.skip_reasons)