[Python-Dev] PEP: Frequently-requested additional features for the `unittest` module
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:
* 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
------------------------------------
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:
* ``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 :
More information about the Python-Dev
mailing list