Issue 7832: renaming unittest.TestCase.assertSameElements to assertItemsEqual and changing behaviour

This commit is contained in:
Michael Foord 2010-03-20 03:00:34 +00:00
parent 2e6d2622bd
commit 98e7b7644b
6 changed files with 144 additions and 68 deletions

View File

@ -786,7 +786,7 @@ Test cases
will be included in the error message. This method is used by default
when comparing Unicode strings with :meth:`assertEqual`.
If specified *msg* will be used as the error message on failure.
If specified, *msg* will be used as the error message on failure.
.. versionadded:: 2.7
@ -807,22 +807,24 @@ Test cases
Tests that *first* is or is not in *second* with an explanatory error
message as appropriate.
If specified *msg* will be used as the error message on failure.
If specified, *msg* will be used as the error message on failure.
.. versionadded:: 2.7
.. method:: assertSameElements(actual, expected, msg=None)
.. method:: assertItemsEqual(actual, expected, msg=None)
Test that sequence *expected* contains the same elements as *actual*,
regardless of their order. When they don't, an error message listing
the differences between the sequences will be generated.
regardless of their order. When they don't, an error message listing the
differences between the sequences will be generated.
Duplicate elements are ignored when comparing *actual* and *expected*.
It is the equivalent of ``assertEqual(set(expected), set(actual))``
but it works with sequences of unhashable objects as well.
Duplicate elements are *not* ignored when comparing *actual* and
*expected*. It verifies if each element has the same count in both
sequences. It is the equivalent of ``assertEqual(sorted(expected),
sorted(actual))`` but it works with sequences of unhashable objects as
well.
If specified *msg* will be used as the error message on failure.
If specified, *msg* will be used as the error message on failure.
.. versionadded:: 2.7
@ -836,7 +838,7 @@ Test cases
Fails if either of *set1* or *set2* does not have a :meth:`set.difference`
method.
If specified *msg* will be used as the error message on failure.
If specified, *msg* will be used as the error message on failure.
.. versionadded:: 2.7
@ -848,7 +850,7 @@ Test cases
method will be used by default to compare dictionaries in
calls to :meth:`assertEqual`.
If specified *msg* will be used as the error message on failure.
If specified, *msg* will be used as the error message on failure.
.. versionadded:: 2.7
@ -859,7 +861,7 @@ Test cases
superset of those in *expected*. If not, an error message listing
the missing keys and mismatched values is generated.
If specified *msg* will be used as the error message on failure.
If specified, *msg* will be used as the error message on failure.
.. versionadded:: 2.7
@ -873,7 +875,7 @@ Test cases
These methods are used by default when comparing lists or tuples with
:meth:`assertEqual`.
If specified *msg* will be used as the error message on failure.
If specified, *msg* will be used as the error message on failure.
.. versionadded:: 2.7
@ -885,7 +887,7 @@ Test cases
be raised. If the sequences are different an error message is
constructed that shows the difference between the two.
If specified *msg* will be used as the error message on failure.
If specified, *msg* will be used as the error message on failure.
This method is used to implement :meth:`assertListEqual` and
:meth:`assertTupleEqual`.

View File

@ -1086,7 +1086,7 @@ GvR worked on merging them into Python's version of :mod:`unittest`.
* :meth:`assertIn` and :meth:`assertNotIn` tests whether
*first* is or is not in *second*.
* :meth:`assertSameElements` tests whether two provided sequences
* :meth:`assertItemsEqual` tests whether two provided sequences
contain the same elements.
* :meth:`assertSetEqual` compares whether two sets are equal, and

View File

@ -135,18 +135,18 @@ class CgiTests(unittest.TestCase):
if isinstance(expect, dict):
# test dict interface
self.assertEqual(len(expect), len(fcd))
self.assertSameElements(expect.keys(), fcd.keys())
self.assertSameElements(expect.values(), fcd.values())
self.assertSameElements(expect.items(), fcd.items())
self.assertItemsEqual(expect.keys(), fcd.keys())
self.assertItemsEqual(expect.values(), fcd.values())
self.assertItemsEqual(expect.items(), fcd.items())
self.assertEqual(fcd.get("nonexistent field", "default"), "default")
self.assertEqual(len(sd), len(fs))
self.assertSameElements(sd.keys(), fs.keys())
self.assertItemsEqual(sd.keys(), fs.keys())
self.assertEqual(fs.getvalue("nonexistent field", "default"), "default")
# test individual fields
for key in expect.keys():
expect_val = expect[key]
self.assertTrue(fcd.has_key(key))
self.assertSameElements(fcd[key], expect[key])
self.assertItemsEqual(fcd[key], expect[key])
self.assertEqual(fcd.get(key, "default"), fcd[key])
self.assertTrue(fs.has_key(key))
if len(expect_val) > 1:
@ -162,11 +162,11 @@ class CgiTests(unittest.TestCase):
self.assertTrue(single_value)
self.assertEqual(val, expect_val[0])
self.assertEqual(fs.getvalue(key), expect_val[0])
self.assertSameElements(sd.getlist(key), expect_val)
self.assertItemsEqual(sd.getlist(key), expect_val)
if single_value:
self.assertSameElements(sd.values(),
self.assertItemsEqual(sd.values(),
first_elts(expect.values()))
self.assertSameElements(sd.items(),
self.assertItemsEqual(sd.items(),
first_second_elts(expect.items()))
def test_weird_formcontentdict(self):
@ -178,7 +178,7 @@ class CgiTests(unittest.TestCase):
self.assertEqual(d[k], v)
for k, v in d.items():
self.assertEqual(expect[k], v)
self.assertSameElements(expect.values(), d.values())
self.assertItemsEqual(expect.values(), d.values())
def test_log(self):
cgi.log("Testing")

View File

@ -2575,9 +2575,9 @@ class Test_TestCase(TestCase, TestEquality, TestHashing):
class SadSnake(object):
"""Dummy class for test_addTypeEqualityFunc."""
s1, s2 = SadSnake(), SadSnake()
self.assertFalse(s1 == s2)
self.assertNotEqual(s1, s2)
def AllSnakesCreatedEqual(a, b, msg=None):
return type(a) == type(b) == SadSnake
return type(a) is type(b) is SadSnake
self.addTypeEqualityFunc(SadSnake, AllSnakesCreatedEqual)
self.assertEqual(s1, s2)
# No this doesn't clean up and remove the SadSnake equality func
@ -2745,21 +2745,51 @@ class Test_TestCase(TestCase, TestEquality, TestHashing):
self.assertRaises(self.failureException, self.assertDictEqual, [], d)
self.assertRaises(self.failureException, self.assertDictEqual, 1, 1)
self.assertSameElements([1, 2, 3], [3, 2, 1])
self.assertSameElements([1, 2] + [3] * 100, [1] * 100 + [2, 3])
self.assertSameElements(['foo', 'bar', 'baz'], ['bar', 'baz', 'foo'])
self.assertRaises(self.failureException, self.assertSameElements,
def testAssertItemsEqual(self):
a = object()
self.assertItemsEqual([1, 2, 3], [3, 2, 1])
self.assertItemsEqual(['foo', 'bar', 'baz'], ['bar', 'baz', 'foo'])
self.assertItemsEqual([a, a, 2, 2, 3], (a, 2, 3, a, 2))
self.assertItemsEqual([1, "2", "a", "a"], ["a", "2", True, "a"])
self.assertRaises(self.failureException, self.assertItemsEqual,
[1, 2] + [3] * 100, [1] * 100 + [2, 3])
self.assertRaises(self.failureException, self.assertItemsEqual,
[1, "2", "a", "a"], ["a", "2", True, 1])
self.assertRaises(self.failureException, self.assertItemsEqual,
[10], [10, 11])
self.assertRaises(self.failureException, self.assertSameElements,
self.assertRaises(self.failureException, self.assertItemsEqual,
[10, 11], [10])
self.assertRaises(self.failureException, self.assertItemsEqual,
[10, 11, 10], [10, 11])
# Test that sequences of unhashable objects can be tested for sameness:
self.assertSameElements([[1, 2], [3, 4]], [[3, 4], [1, 2]])
self.assertSameElements([{'a': 1}, {'b': 2}], [{'b': 2}, {'a': 1}])
self.assertRaises(self.failureException, self.assertSameElements,
self.assertItemsEqual([[1, 2], [3, 4], 0], [False, [3, 4], [1, 2]])
with test_support.check_warnings(quiet=True) as w:
# hashable types, but not orderable
self.assertRaises(self.failureException, self.assertItemsEqual,
[], [divmod, 'x', 1, 5j, 2j, frozenset()])
# comparing dicts raises a py3k warning
self.assertItemsEqual([{'a': 1}, {'b': 2}], [{'b': 2}, {'a': 1}])
# comparing heterogenous non-hashable sequences raises a py3k warning
self.assertItemsEqual([1, 'x', divmod, []], [divmod, [], 'x', 1])
self.assertRaises(self.failureException, self.assertItemsEqual,
[], [divmod, [], 'x', 1, 5j, 2j, set()])
# fail the test if warnings are not silenced
if w.warnings:
self.fail('assertItemsEqual raised a warning: ' +
str(w.warnings[0]))
self.assertRaises(self.failureException, self.assertItemsEqual,
[[1]], [[2]])
# Same elements, but not same sequence length
self.assertRaises(self.failureException, self.assertItemsEqual,
[1, 1, 2], [2, 1])
self.assertRaises(self.failureException, self.assertItemsEqual,
[1, 1, "2", "a", "a"], ["2", "2", True, "a"])
self.assertRaises(self.failureException, self.assertItemsEqual,
[1, {'b': 2}, None, True], [{'b': 2}, True, None])
def testAssertSetEqual(self):
set1 = set()
set2 = set()
@ -3009,13 +3039,14 @@ test case
Do not use these methods. They will go away in 3.3.
"""
self.failIfEqual(3, 5)
self.failUnlessEqual(3, 3)
self.failUnlessAlmostEqual(2.0, 2.0)
self.failIfAlmostEqual(3.0, 5.0)
self.failUnless(True)
self.failUnlessRaises(TypeError, lambda _: 3.14 + u'spam')
self.failIf(False)
with test_support.check_warnings():
self.failIfEqual(3, 5)
self.failUnlessEqual(3, 3)
self.failUnlessAlmostEqual(2.0, 2.0)
self.failIfAlmostEqual(3.0, 5.0)
self.failUnless(True)
self.failUnlessRaises(TypeError, lambda _: 3.14 + u'spam')
self.failIf(False)
def testDeepcopy(self):
# Issue: 5660
@ -3355,8 +3386,8 @@ class TestLongMessage(TestCase):
"^Missing: 'key'$",
"^Missing: 'key' : oops$"])
def testAssertSameElements(self):
self.assertMessages('assertSameElements', ([], [None]),
def testAssertItemsEqual(self):
self.assertMessages('assertItemsEqual', ([], [None]),
[r"\[None\]$", "^oops$",
r"\[None\]$",
r"\[None\] : oops$"])

View File

@ -8,8 +8,9 @@ import re
import warnings
from . import result
from .util import strclass, safe_repr, sorted_list_difference
from .util import (
strclass, safe_repr, sorted_list_difference, unorderable_list_difference
)
class SkipTest(Exception):
"""
@ -686,10 +687,9 @@ class TestCase(object):
msg: Optional message to use on failure instead of a list of
differences.
For more general containership equality, assertSameElements will work
with things other than sets. This uses ducktyping to support
different types of sets, and is optimized for sets specifically
(parameters must support a difference method).
assertSetEqual uses ducktyping to support different types of sets, and
is optimized for sets specifically (parameters must support a
difference method).
"""
try:
difference1 = set1.difference(set2)
@ -784,42 +784,48 @@ class TestCase(object):
self.fail(self._formatMessage(msg, standardMsg))
def assertSameElements(self, expected_seq, actual_seq, msg=None):
"""An unordered sequence specific comparison.
def assertItemsEqual(self, expected_seq, actual_seq, msg=None):
"""An unordered sequence / set specific comparison. It asserts that
expected_seq and actual_seq contain the same elements. It is
the equivalent of::
self.assertEqual(sorted(expected_seq), sorted(actual_seq))
Raises with an error message listing which elements of expected_seq
are missing from actual_seq and vice versa if any.
Duplicate elements are ignored when comparing *expected_seq* and
*actual_seq*. It is the equivalent of ``assertEqual(set(expected),
set(actual))`` but it works with sequences of unhashable objects as
well.
Asserts that each element has the same count in both sequences.
Example:
- [0, 1, 1] and [1, 0, 1] compare equal.
- [0, 0, 1] and [0, 1] compare unequal.
"""
with warnings.catch_warnings():
if sys.py3kwarning:
# Silence Py3k warning raised during the sorting
for _msg in ["dict inequality comparisons",
"builtin_function_or_method order comparisons",
"comparing unequal types"]:
"builtin_function_or_method order comparisons",
"comparing unequal types"]:
warnings.filterwarnings("ignore", _msg, DeprecationWarning)
try:
expected = set(expected_seq)
actual = set(actual_seq)
missing = sorted(expected.difference(actual))
unexpected = sorted(actual.difference(expected))
except TypeError:
# Fall back to slower list-compare if any of the objects are
# not hashable.
expected = sorted(expected_seq)
actual = sorted(actual_seq)
missing, unexpected = sorted_list_difference(expected, actual)
except TypeError:
# Unsortable items (example: set(), complex(), ...)
expected = list(expected_seq)
actual = list(actual_seq)
missing, unexpected = unorderable_list_difference(
expected, actual, ignore_duplicate=False
)
else:
return self.assertSequenceEqual(expected, actual, msg=msg)
errors = []
if missing:
errors.append('Expected, but missing:\n %s' %
safe_repr(missing))
safe_repr(missing))
if unexpected:
errors.append('Unexpected, but present:\n %s' %
safe_repr(unexpected))
safe_repr(unexpected))
if errors:
standardMsg = '\n'.join(errors)
self.fail(self._formatMessage(msg, standardMsg))

View File

@ -48,3 +48,40 @@ def sorted_list_difference(expected, actual):
unexpected.extend(actual[j:])
break
return missing, unexpected
def unorderable_list_difference(expected, actual, ignore_duplicate=False):
"""Same behavior as sorted_list_difference but
for lists of unorderable items (like dicts).
As it does a linear search per item (remove) it
has O(n*n) performance.
"""
missing = []
unexpected = []
while expected:
item = expected.pop()
try:
actual.remove(item)
except ValueError:
missing.append(item)
if ignore_duplicate:
for lst in expected, actual:
try:
while True:
lst.remove(item)
except ValueError:
pass
if ignore_duplicate:
while actual:
item = actual.pop()
unexpected.append(item)
try:
while True:
actual.remove(item)
except ValueError:
pass
return missing, unexpected
# anything left in actual is unexpected
return missing, actual