Source code for dectest.suite

"""

import functools
import logging
import sys

from . import config as mconfig

class TestSuite():
    """
[docs] This is the main test suite class. It should be asigned to a variable at import time, and can then be used to reference decorators that can be used for detailing any tests created. """ class TestCase(): """
[docs] An individual testcase. Generated on command, as a wrapper for the data stored by the :class:`TestSuite` """ def __init__(self, name, parent): """ Initialises state. """ self.name = name self.parent = parent def input(self, *args, **kwargs): """
[docs] Sets the input for the test function when testing commenses. """ self.parent._future_tests[self.name]['input'] = (args, kwargs) return self._blank_decorator def out(self, out=None): """
[docs] Sets the expected output of the test function. """ self.parent._future_tests[self.name]["output"] = out return self._blank_decorator def __getattr__(self, name): if name in self.parent._sideaffect_tests:
sat = self.parent._sideaffect_tests[name]() self.parent._future_tests[self.name]["sideaffects"].append( sat) return sat.decorator raise AttributeError() def setfunc(self, func): """
[docs] Sets the function to test. """ self.parent._future_tests[self.name]["func"] = func def test(self): """
[docs] Runs the specific test case. """ args, kwargs = self.input assert self.test_func(*args, **kwargs) == self.out, \ "TestCase {0} failed".format(self.name) @staticmethod def _blank_decorator(func):
""" Just a decorator that does nothing. """ return func _sideaffect_tests = {}
def __init__(self, name, config=None, logger=None): """ Initialises the test suite, mainly populating some hidden attributes. """ if config is None: config = mconfig.DefaultConfig() if logger is None: logger = logging.getLogger(name) self._config = config self._name = name self._future_tests = {} for name in self._config.get_list('testing', 'sideaffects') or []: sat = self._config.get_python(name) if not sat: raise Warning("Could not find side affect test named" + name) self._sideaffect_tests[sat.name] = sat self._run_tests = self._config.get_bool('testing', 'runtests') or \ self._config.get_default('testing', 'runtests') def test(self): """
[docs] Runs all the test cases. """ if not self._run_tests: return print "Test Suite '{0}'".format(self._name) print "=" * 80 fails = 0 for name, tc in self._future_tests.iteritems(): for test in tc["sideaffects"]: test.pre_test() args, kwargs = tc["input"] output = tc["func"](*args, **kwargs) failed = False failed = failed or not output == tc["output"] for test in tc["sideaffects"]: failed = failed or not test.test() if failed: sys.stdout.write('f') fails += 1 else: sys.stdout.write('.') print "\n", print "=" * 80 if fails == 0: print "All tests passed successfully" else: if fails == 1: print "1 test failed" else: print "{0} tests failed".format(fails) def register(self, name, method=False): """
[docs] Creates a new test case, with the given name. The :class:`TestCase` object will be availible as an attribute of the :class:`TestSuite` with the same name as the new test case. >>> ts = TestSuite() >>> @ts.register("tc") ... def test(): ... return ... >>> ts.tc <dectest.suite.TestCase instance at 0xb71fcc2c> .. important:: If the decorated object is a method of a class, then the `method` argument must be true. This is because dectest cannot obtain a copy of an instance of the class untill runtime, so we need to know if to catch the `self` variable. """ if name in self._future_tests: print("Cannot register the same test case twice.") return self._blank_decorator self._future_tests[name] = {} self._future_tests[name]["input"] = (), {} self._future_tests[name]["output"] = None self._future_tests[name]["sideaffects"] = [] def decorator(func): """ The decorator for the test function. """ self._future_tests[name]["func"] = func self._future_tests[name]["method"] = method self._future_tests[name]["self"] = None func.tested = False @functools.wraps(func) def test_dec(*args, **kwargs): if method and not self._future_tests[name]["self"]: self._future_tests[name]["self"] = args[0] if not func.tested and self._run_tests and \ self._config.get_bool("testing", "testasrun"): self._setup_test(name) passed = self._test_func(name) self._teardown_test(name) self._log_result(name, passed) func.tested = True return func(*args, **kwargs) return test_dec return decorator def _setup_test(self, name): """
Runs all the pre_test methods on all the registered sideaffect tests. Allso calls the pre test callback, which can be set at configuration value `testing.pretest`. """ pretest = self._config.get("testing", "pretest") if pretest: obj = self._config.get_python(pretest) if callable(obj): obj() else: raise Warning("Pre-test callback was not callable") tc = self._future_tests[name] for test in tc["sideaffects"]: test.pre_test() def _test_func(self, name): """ Runs the test case with name `name`. Returns `True` if the test case passed, `False` otherwise. """ tc = self._future_tests[name] args, kwargs = tc["input"] if tc["method"]: args = (tc["self"],) + args output = tc["func"](*args, **kwargs) failed = False failed = failed or not output == tc["output"] for test in tc["sideaffects"]: failed = failed or not test.test() return not failed def _teardown_test(self, name): """ Tears down after the test has been run. This runs the post test callback, which can be set at confiuration value `testing.posttest`. """ posttest = self._config.get("testing", "posttest") if posttest: obj = self._config.get_python(posttest) if callable(obj): obj() else: raise Warning("Post-test callback was not callable") def _log_result(self, name, passed): """ Logs the result of a test of a function. """ print "Test case {0} ".format(name) + ("passed" if passed else "failed") def __getattr__(self, name): if name in self._future_tests: return self.TestCase(name, self) else: raise AttributeError("No test case {0}".format(name))