Significant changes: Add a new 'TestLoader.load_tests_from_collection' method, with full reference implementation. This makes the 'run_tests' reference implementation straightforward.
:PEP: XXX :Title: Frequently-requested additional features for the `unittest` module :Version: 0.4 :Last-Modified: 2008-07-16 :Author: Ben Finney <[EMAIL PROTECTED]> :Status: Draft :Type: Standards Track :Content-Type: test/x-rst :Requires: PEP XXX (Consolidating names in the `unittest` module) :Created: 2008-07-14 :Post-History: .. contents:: Abstract ======== This PEP proposes frequently-requested additions to the standard library `unittest` module that are natural extensions of its existing functionality. Motivation ========== The `unittest` module is functionally complete. Nevertheless, many users request and devise extensions to perform functions that are so common they deserve to have a standard implementation. Specification ============= New condition tests ------------------- The following test methods will be added to the ``TestCase`` class. The function signature is part of this specification. The body is to be treated as a reference implementation only; any functionally identical implementation also meets this specification. :: import operator import re class TestCase(object): # … def assert_compare_true(op, first, second, msg=None): if msg is None: msg = "%(first)r %(op)r %(second)" % vars() if not op(first, second): raise self.failure_exception(msg) def assert_in(container, member, msg=None): op = operator.__contains__ self.assert_compare_true(op, container, member, msg) def assert_is(first, second, msg=None): op = operator.is_ self.assert_compare_true(op, first, second, msg) def assert_less_than(first, second, msg=None): op = operator.lt self.assert_compare_true(op, first, second, msg) def assert_greater_than(first, second, msg=None): op = operator.gt self.assert_compare_true(op, first, second, msg) def assert_less_than_or_equal(first, second, msg=None): op = operator.le self.assert_compare_true(op, first, second, msg) def assert_greater_than_or_equal(first, second, msg=None): op = operator.ge self.assert_compare_true(op, first, second, msg) def assert_members_equal(first, second, msg=None): self.assert_equal(set(first), set(second), msg) def assert_sequence_equal(first, second, msg=None): self.assert_equal(list(first), list(second), msg) def assert_raises_with_message_regex( exc_class, message_regex, callable_obj, *args, **kwargs): exc_name = exc_class.__name__ message_pattern = re.compile(message_regex) try: callable_obj(*args, **kwargs) except exc_class, exc: exc_message = str(exc) if not message_pattern.match(exc_message): msg = ( "%(exc_name)s raised" " without message matching %(message_regex)r" " (got message %(exc_message)r)" ) % vars() raise self.failure_exception(msg) else: msg = "%(exc_name)s not raised" % vars() raise self.failure_exception(msg) The following test methods are also added. Their implementation in each case is simply the logical inverse of a corresponding method above. :: def assert_compare_false(op, first, second, msg=None): # Logical inverse of assert_compare_true def assert_not_in(container, member, msg=None): # Logical inverse of assert_in def assert_is_not(first, second, msg=None) # Logical inverse of assert_is def assert_not_less_than(first, second, msg=None) # Logical inverse of assert_less_than def assert_not_greater_than(first, second, msg=None) # Logical inverse of assert_greater_than def assert_not_less_than_or_equal(first, second, msg=None) # Logical inverse of assert_less_than_or_equal def assert_not_greater_than_or_equal(first, second, msg=None) # Logical inverse of assert_greater_than_or_equal def assert_members_not_equal(first, second, msg=None) # Logical inverse of assert_members_equal def assert_sequence_not_equal(first, second, msg=None) # Logical inverse of assert_sequence_equal Enhanced failure message for equality tests ------------------------------------------- The equality tests will change their behaviour such that the message always, even if overridden with a specific message when called, includes extra information: * For both ``assert_equal`` and ``assert_not_equal``: the ``repr()`` output of the objects that were compared. * For ``assert_equal`` comparisons of ``basestring`` instances that are multi-line text: the output of ``diff`` comparing the two texts. * For membership comparisons with ``assert_*_equal``: the ``repr()`` output of the members that were not equal in each collection. (This change is not done for the corresponding ``assert_*_not_equal`` tests, which only fail if the collection members are equal.) Simple invocation of test collection ------------------------------------ To allow simpler loading and running of test cases from an arbitrary collection, the following new functionality will be added to the `unittest` module. The function signatures are part of this specification; the implementation is to be considered a reference implementation only. :: class TestLoader(object): # … def load_tests_from_collection(self, collection): """ Return a suite of all test cases found in `collection` :param collection: One of the following: * a `TestSuite` instance * a `TestCase` subclass * a module * an iterable producing any of these types :return: A `TestSuite` instance containing all the test cases found recursively within `collection`. """ suite = None if isinstance(collection, TestSuite): suite = collection elif isinstance(collection, type): if issubclass(collection, TestCase): suite = self.load_tests_from_test_case(collection) elif isinstance(collection, types.ModuleType): suite = self.load_tests_from_module(collection) elif hasattr(collection, '__iter__'): suite = self.suite_class() for item in collection: suite.add_test(self.load_tests_from_collection(item)) else: msg = "not a test collection: %(collection)r" % vars() raise TypeError(msg) return suite def run_tests( tests, loader_class=TestLoader, runner_class=TextTestRunner): """ Run a collection of tests with a test runner :param tests: A test collection as defined by the `loader_class` method `load_tests_from_collection`. :param loader_class: The type of test loader to use for collecting tests from the `tests` collection. :param runner_class: The type of test runner to instantiate for running the collected test suite. :return: None. """ loader = loader_class() suite = loader.load_tests_from_collection(tests) runner = runner_class() runner.run(suite) Rationale ========= Names for logical-inverse tests ------------------------------- The simple pattern established by ``assert_foo`` having a logical inverse named ``assert_not_foo`` sometimes results in gramatically awkward names. The following names were chosen in exception of this pattern, in the interest of the API names being more intuitive: * ``assert_is_not`` * ``assert_members_not_equal`` * ``assert_sequence_not_equal`` Order of method parameters -------------------------- The methods ``assert_in``, ``assert_not_in`` have the container as the first parameter. This makes the grammatical object of the function name come immediately after the function name: "Assert in `container`". This matches the convention set by the existing ``assert_raises(exception, callable_obj, …)`` "(Assert the code raises `exception`"). Backwards Compatibility ======================= This PEP proposes only additional features. There are no backward-incompatible changes. Reference Implementation ======================== None yet. Copyright ========= This document is hereby placed in the public domain by its author. .. Local Variables: mode: rst coding: utf-8 End: vim: filetype=rst : _______________________________________________ Python-Dev mailing list Python-Dev@python.org http://mail.python.org/mailman/listinfo/python-dev Unsubscribe: http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com