2016-02-06 03:12:25 +01:00

238 lines
8.2 KiB
Python

"""
Load tests from classes that are *not* :class:`unittest.TestCase` subclasses.
This plugin responds to :func:`loadTestsFromModule` by adding test
cases for test methods found in classes in the module that are *not*
sublcasses of :class:`unittest.TestCase`, but whose names (lowercased)
match the configured test method prefix.
Test class methods that are generators or have param lists are not
loaded here, but by the :class:`nose2.plugins.loader.generators.Generators` and
:class:`nose2.plugins.loader.parameters.Parameters` plugins.
This plugin also implements :func:`loadTestsFromName` to enable
loading tests from dotted class and method names passed on the command
line.
This plugin makes two additional plugin hooks available for other
test loaders to use:
.. function :: loadTestsFromTestClass(self, event)
:param event: A :class:`LoadFromTestClassEvent` instance
Plugins can use this hook to load tests from a class that is not a
:class:`unittest.TestCase` subclass. To prevent other plugins from
loading tests from the test class, set ``event.handled`` to True and
return a test suite. Plugins can also append tests to
``event.extraTests`` -- ususally that's what you want to do, since
that will allow other plugins to load their tests from the test
case as well.
.. function :: getTestMethodNames(self, event)
:param event: A :class:`GetTestMethodNamesEvent` instance
Plugins can use this hook to limit or extend the list of test case
names that will be loaded from a class that is not a
:class:`unittest.TestCase` subclass by the standard nose2 test
loader plugins (and other plugins that respect the results of the
hook). To force a specific list of names, set ``event.handled`` to
True and return a list: this exact list will be the only test case
names loaded from the test case. Plugins can also extend the list
of names by appending test names to ``event.extraNames``, and
exclude names by appending test names to ``event.excludedNames``.
About Test Classes
------------------
Test classes are classes that look test-like but are not subclasses of
:class:`unittest.TestCase`. Test classes support all of the same test
types and fixtures as test cases.
To "look test-like" a class must have a name that, lowercased, matches
the configured test method prefix -- "test" by default. Test classes
must also be able to be instantiated without arguments.
What are they useful for? Mostly the case where a test class can't for
some reason subclass :class:`unittest.TestCase`. Otherwise, test class
tests and test cases are functionally equivalent in nose2, and test
cases have broader support and all of those helpful *assert\** methods
-- so when in doubt, you should use a :class:`unittest.TestCase`.
Here's an example of a test class::
class TestSomething(object):
def test(self):
assert self.something(), "Something failed!"
"""
import unittest
import sys
from nose2 import events, util
from nose2.compat import unittest as ut2
__unittest = True
class TestClassLoader(events.Plugin):
"""Loader plugin that loads test functions"""
alwaysOn = True
configSection = 'test-classes'
def registerInSubprocess(self, event):
event.pluginClasses.append(self.__class__)
def register(self):
"""Install extra hooks
Adds the new plugin hooks:
- loadTestsFromTestClass
- getTestMethodNames
"""
super(TestClassLoader, self).register()
self.addMethods('loadTestsFromTestClass', 'getTestMethodNames')
def loadTestsFromModule(self, event):
"""Load test classes from event.module"""
module = event.module
for name in dir(module):
obj = getattr(module, name)
if (isinstance(obj, type) and
not issubclass(obj, unittest.TestCase) and
not issubclass(obj, unittest.TestSuite) and
name.lower().startswith(self.session.testMethodPrefix)):
event.extraTests.append(
self._loadTestsFromTestClass(event, obj))
def loadTestsFromName(self, event):
"""Load tests from event.name if it names a test class/method"""
name = event.name
module = event.module
try:
result = util.test_from_name(name, module)
except (AttributeError, ImportError) as e:
event.handled = True
return event.loader.failedLoadTests(name, e)
if result is None:
return
parent, obj, name, index = result
if isinstance(obj, type) and not issubclass(obj, unittest.TestCase):
# name is a test case class
event.extraTests.append(self._loadTestsFromTestClass(event, obj))
elif (isinstance(parent, type) and
not issubclass(parent, unittest.TestCase) and
not util.isgenerator(obj) and
not hasattr(obj, 'paramList')):
# name is a single test method
event.extraTests.append(
util.transplant_class(
MethodTestCase(parent), parent.__module__)(obj.__name__))
def _loadTestsFromTestClass(self, event, cls):
# ... fire event for others to load from
evt = LoadFromTestClassEvent(event.loader, cls)
result = self.session.hooks.loadTestsFromTestClass(evt)
if evt.handled:
loaded_suite = result or event.loader.suiteClass()
else:
names = self._getTestMethodNames(event, cls)
try:
loaded_suite = event.loader.suiteClass(
[util.transplant_class(
MethodTestCase(cls), cls.__module__)(name)
for name in names])
except:
_, ev, _ = sys.exc_info()
return event.loader.suiteClass(
event.loader.failedLoadTests(cls.__name__, ev))
if evt.extraTests:
loaded_suite.addTests(evt.extraTests)
# ... add extra tests
return loaded_suite
def _getTestMethodNames(self, event, cls):
# ... give others a chance to modify list
excluded = set()
def isTestMethod(attrname, cls=cls, excluded=excluded):
# FIXME allow plugs to change prefix
prefix = self.session.testMethodPrefix
return (
attrname.startswith(prefix) and
hasattr(getattr(cls, attrname), '__call__') and
attrname not in excluded
)
evt = GetTestMethodNamesEvent(event.loader, cls, isTestMethod)
result = self.session.hooks.getTestMethodNames(evt)
if evt.handled:
test_names = result or []
else:
excluded.update(evt.excludedNames)
test_names = [entry for entry in dir(cls)
if isTestMethod(entry)]
if event.loader.sortTestMethodsUsing:
test_names.sort(key=event.loader.sortTestMethodsUsing)
return test_names
# to prevent unit2 discover from running this as a test, need to
# hide it inside of a factory func. ugly!
def MethodTestCase(cls):
class _MethodTestCase(ut2.TestCase):
def __init__(self, method):
self.method = method
self._name = "%s.%s.%s" % (cls.__module__, cls.__name__, method)
self.obj = cls()
ut2.TestCase.__init__(self, 'runTest')
@classmethod
def setUpClass(klass):
if hasattr(cls, 'setUpClass'):
cls.setUpClass()
@classmethod
def tearDownClass(klass):
if hasattr(cls, 'tearDownClass'):
cls.tearDownClass()
def setUp(self):
if hasattr(self.obj, 'setUp'):
self.obj.setUp()
def tearDown(self):
if hasattr(self.obj, 'tearDown'):
self.obj.tearDown()
def __repr__(self):
return self._name
id = __str__ = __repr__
def runTest(self):
getattr(self.obj, self.method)()
return _MethodTestCase
#
# Event classes
#
class LoadFromTestClassEvent(events.LoadFromTestCaseEvent):
"""Bare subclass of :class:`nose2.events.LoadFromTestCaseEvent`"""
class GetTestMethodNamesEvent(events.GetTestCaseNamesEvent):
"""Bare subclass of :class:`nose2.events.GetTestCaseNamesEvent`"""