# -*- coding: utf-8 -*-
import logging
import unittest
from types import FunctionType
from noseapp.core import loader
from noseapp.core import extensions
from noseapp.suite.context import SuiteContext
from noseapp.case.screenplay import ScreenPlayCase
from noseapp.case.decorators import flows as _flows
from noseapp.case.base import make_test_case_class_from_function
logger = logging.getLogger(__name__)
def suite_is_mount(suite):
"""
If suite is mounting to app then True else False.
:param suite: suite instance
:type suite: Suite
:rtype: bool
"""
return bool(suite.of_app)
[docs]class Suite(object):
"""
Base Suite class for group or one TestCase.
Usage::
suite = Suite(__name__)
@suite.register
class Case(suite.TestCase):
def runTest(self):
pass
@suite.register
def my_test_case(case):
case.assertTrue(True)
@suite.register(simple=True)
def simple_test():
assert True
"""
# Constant is pre set for require param
DEFAULT_REQUIRE = None
# This class is base class for making test case from function
test_case_class = ScreenPlayCase
[docs] def __init__(self, name, require=None, context=None):
"""
:param name: suite name
:type name: str
:param require: extension names
:type require: list
:param context: context instance
:type context: noseapp.suite.context.SuiteContext
"""
# Suite name. Must be str only
self.__name = name
# of app name
self.__of_app = None
# Was suite built? True or False
self.__is_build = False
# Context of this instance
self.__context = context or SuiteContext(
list(self.DEFAULT_REQUIRE or []) + list(require or []),
)
# Push callbacks to context. They'll be called with run nose suite.
self.__context.add_setup(lambda: self.setUp())
self.__context.add_teardown(lambda: self.tearDown())
@property
def name(self):
"""
Suite name.
If suite is mounting to app then name
is <app_name>.<suite_name> else <suite_name>.
"""
if self.__of_app:
return '{}.{}'.format(
self.__of_app, self.__name,
)
return self.__name
@property
def of_app(self):
"""
Name of mounted application.
:rtype: str or None
"""
return self.__of_app
@property
def context(self):
"""
Suite context.
:rtype: noseapp.suite.context.SuiteContext
"""
return self.__context
@property
def is_build(self):
"""
If suite is building return True else False.
:rtype: bool
"""
return self.__is_build
@property
def test_cases(self):
"""
Test cases storage of context.
:rtype: list
"""
return self.__context.test_cases
@property
def require(self):
"""
Extensions names storage of context.
:rtype: list
"""
return self.__context.require
@property
def status(self):
"""
Get suite status.
If suite is mounted to application
then 'mounted' else 'unmounted'.
:rtype: str
"""
return 'mounted' if self.__of_app else 'unmounted'
[docs] def mount_to_app(self, app):
"""
:param app: application instance
:type app: noseapp.app.base.NoseApp
:raises: RuntimeError
"""
if suite_is_mount(self):
raise RuntimeError(
'{} already mount to <NoseApp {}>. '.format(self, self.__of_app),
)
self.__of_app = app.name
app.context.add_suite(self)
@property
def TestCase(self):
"""
Get test case class from suite instance.
Will be return a test_case_class alias.
The alias is base class for making test case from function.
:rtype: noseapp.case.screenplay.ScreenPlayCase
"""
return self.test_case_class
[docs] def setUp(self):
"""
Callback. Will be called run before run suite.
"""
pass
[docs] def tearDown(self):
"""
Callback. Will be called run after run suite.
"""
pass
@property
def skip(self):
"""
Proxy to unittest.skip.
Example::
class Test(suite.TestCase):
@suite.skip('TODO: ...')
def test(self):
pass
:rtype: unittest.skip
"""
return unittest.skip
@property
def skip_if(self):
"""
Proxy to unittest.skipIf.
:rtype: unittest.skipIf
"""
return unittest.skipIf
@property
def skip_unless(self):
"""
Proxy to unittest.skipUnless.
:rtype: unittest.skipUnless
"""
return unittest.skipUnless
# unit test style
skipIf = skip_if
skipUnless = skip_unless
[docs] def add_pre_run(self, func):
"""
Add function for pre run test case to context.
Example::
@suite.add_pre_run
def pre_run(case):
case.do_something()
# or
suite.add_pre_run(lambda case: case.do_something())
"""
self.__context.add_pre_run(func)
return func
[docs] def add_post_run(self, func):
"""
Add function for post run test case to context.
Example::
@suite.add_post_run
def post_run(case):
case.do_something()
# or
suite.add_post_run(lambda case: case.do_something())
"""
self.__context.add_post_run(func)
return func
[docs] def add_setup(self, func):
"""
Add setup callback to context.
Example::
@suite.add_setup
def setup():
pass
# or
suite.add_setup(lambda: None)
"""
self.__context.add_setup(func)
return func
[docs] def add_teardown(self, func):
"""
Add teardown callback to context.
Example::
@suite.add_teardown
def teardown():
pass
# or
suite.add_teardown(lambda: None)
"""
self.__context.add_teardown(func)
return func
[docs] def ext(self, name):
"""
Get extension by name.
Extensions will be available after build suite.
Example::
class MySuite(Suite):
DEFAULT_REQUIRE = [
'extension',
]
def setUp(self):
ext = self.ext('extension')
:param name: extension name
:type name: str
:raises: RuntimeError, noseapp.core.extensions.ExtensionNotRequired
"""
if not self.__is_build:
raise RuntimeError(
'Extensions is not available. Suite is not building.',
)
if name not in self.__context.require:
raise extensions.ExtensionNotRequired(name)
return self.__context.extensions.get(name)
[docs] def register(self, cls=None, **kwargs):
"""
Add test case to context.
Example::
@suite.register
class TestCase(suite.TestCase):
def test(self):
pass
# Skip test
@suite.register(skip='TODO: ...')
def test(case):
pass
# Require extension for test case only
@suite.register(require=['extension'])
def test_require_ex(case):
ext = case.ext('extension')
:param cls: test case class
:type cls: noseapp.case.TestCase or function
:param simple: use for simple test function
:type simple: bool
:param skip: if to set then skip case
:type skip: str
:param require: extensions names
:type require: list
:param flows: flows list
:type flows: list, tuple
:raises: TypeError, RuntimeError
:rtype: cls
"""
if not cls and not kwargs:
raise TypeError('cls param or **kwargs is required')
elif cls and kwargs:
raise TypeError('**kwargs can not be used with cls param')
def wrapped(_class, simple=False, skip=None, require=None, flows=None):
if type(_class) == FunctionType:
_class = make_test_case_class_from_function(
_class,
simple=simple,
base_class=self.TestCase,
)
if flows:
for method_name in loader.load_test_names_from_test_case(_class):
setattr(_class, method_name, _flows(*flows)(getattr(_class, method_name)))
if skip:
unittest.skip(skip)(_class)
if hasattr(_class, 'REQUIRE'):
raise RuntimeError('"REQUIRE" attribute can not be pre set')
if require:
setattr(_class, 'REQUIRE', require)
self.__context.add_test_case(_class)
logger.debug(
'Register test case "{}" on {}'.format(
_class.__name__, repr(self),
),
)
return _class
if cls:
return wrapped(cls)
def wrapper(_class):
return wrapped(_class, **kwargs)
return wrapper
[docs] def get_map(self):
"""
Get map of test classes.
::
{
'class name': {
'cls': link_to_class,
'tests': {
'method name': link_to_test_method,
},
},
}
:rtype: dict
"""
mp = {}
for case in self.__context.test_cases:
mp[case.__name__] = {
'cls': case,
'tests': dict(
(atr, getattr(case, atr))
for atr in dir(case)
if atr.startswith(loader.TEST_NAME_PREFIX)
or
atr == loader.DEFAULT_TEST_NAME,
),
}
return mp
[docs] def __call__(self,
program_data,
shuffle=None,
case_name=None,
method_name=None):
"""
Build suite. After call suite instance will be
created instance of nose.suite.ContextSuite
:param program_data: instance of ProgramData
:type program_data: noseapp.core.program.ProgramData
:param shuffle: callable object for randomize test case list
:param case_name: test name for build
:param method_name: test case method name for build
:raises: RuntimeError
:rtype: noseapp.core.suite.base.BaseSuite
"""
if not self.__of_app:
raise RuntimeError('Suite can not be building, not mounted to app')
self.__is_build = True
for ext_name in self.context.require:
ext = extensions.get(ext_name)
self.context.add_extension(ext_name, ext)
if callable(shuffle):
shuffle(self.__context.test_cases)
def make_suites():
suites = []
if case_name:
case = loader.load_case_from_suite(case_name, self)
tests = loader.load_tests_from_test_case(
case.mount_to_suite(self),
method_name=method_name,
)
suites.append(
program_data.suite_class(
tests,
context=case,
config=program_data.config,
pre_run_handlers=self.__context.pre_run,
post_run_handlers=self.__context.post_run,
resultProxy=program_data.result_proxy_factory,
),
)
else:
for case in self.__context.test_cases:
tests = loader.load_tests_from_test_case(
case.mount_to_suite(self),
)
suites.append(
program_data.suite_class(
tests,
context=case,
config=program_data.config,
pre_run_handlers=self.__context.pre_run,
post_run_handlers=self.__context.post_run,
resultProxy=program_data.result_proxy_factory,
),
)
return suites
return program_data.suite_class(
make_suites(),
context=self.__context,
config=program_data.config,
)
def __repr__(self):
return '<Suite({}): {}>'.format(self.status, self.name)