[Python-Dev] PEP: Frequently-requested additional features for the unittest module (original) (raw)

Ben Finney ben+python at benfinney.id.au
Wed Jul 16 03:19:44 CEST 2008


:PEP: XXX :Title: Frequently-requested additional features for the unittest module :Version: 0.3 :Last-Modified: 2008-07-16 :Author: Ben Finney <ben+python at benfinney.id.au> :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 be in the standard module.

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:

Simple invocation of test collection

The following new functionality will be added to the unittest module. The function signature for run_tests is part of this specification; the implementation is to be considered a reference implementation only.

:: def run_tests( tests, loader_class=TestLoader, suite_class=TestSuite, runner_class=TextTestRunner): """ Run a collection of tests with a test runner

        :param tests:
            A sequence of objects that can contain test cases:
            modules, `TestSuite` instances, or `TestCase`
            subclasses.
        :param loader_class:
            The type of test loader to use for collecting tests
            from the `tests` collection.
        :param suite_class:
            The type of test suite to use for accumulating the
            collected test cases.
        :param runner_class:
            The type of test runner to instantiate for running the
            collected test cases.
        :return:
            None.

        """
    def iter_testcases_recursively(collection, loader):
        # Flatten and iterate over collection, generating
        # instances of TestCase
    loader = loader_class()
    suite = suite_class()
    for testcase in iter_testcases_recursively(tests, loader):
        suite.add_tests(testcase)
    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:

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 :



More information about the Python-Dev mailing list