r"""
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*
subclasses 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``. Usually, that's what you want, since
it allows 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 sys
import unittest
from nose2 import events, util
__unittest = True
[docs]class TestClassLoader(events.Plugin):
"""Loader plugin that loads test functions"""
alwaysOn = True
configSection = "test-classes"
def registerInSubprocess(self, event):
event.pluginClasses.append(self.__class__)
[docs] def register(self):
"""Install extra hooks
Adds the new plugin hooks:
- loadTestsFromTestClass
- getTestMethodNames
"""
super(TestClassLoader, self).register()
self.addMethods("loadTestsFromTestClass", "getTestMethodNames")
[docs] 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))
[docs] 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):
event.handled = True
return event.loader.failedLoadTests(name, sys.exc_info())
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 BaseException:
return event.loader.suiteClass(
event.loader.failedLoadTests(cls.__name__, sys.exc_info())
)
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(unittest.TestCase):
def __init__(self, method):
self.method = method
self._name = "%s.%s.%s" % (cls.__module__, cls.__name__, method)
self.obj = cls()
unittest.TestCase.__init__(self, "runTest")
if util.has_class_fixtures(cls):
@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)()
def shortDescription(self):
doc = getattr(self.obj, self.method).__doc__
return doc and doc.split("\n")[0].strip() or None
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`"""