testtools-dev team mailing list archive
-
testtools-dev team
-
Mailing list archive
-
Message #00020
[Merge] lp:~jml/testtools/matcher-fixes into lp:testtools
Jonathan Lange has proposed merging lp:~jml/testtools/matcher-fixes into lp:testtools.
Requested reviews:
testtools developers (testtools-dev)
I was hacking on something else in testtools and wanted to do assertThat(foo, Is(bar)). So I wrote a matcher for Is.
In the process of which, I discovered that we have a bug where assertThat will blow up if the matcher fails, since none of our Mismatch objects provide get_details(). :(
I've fixed this bug, added tests for it, added an Is matcher and done some refactoring to make Equals, NotEquals and Is all pretty much the same in terms of implementation.
--
https://code.launchpad.net/~jml/testtools/matcher-fixes/+merge/31663
Your team testtools developers is requested to review the proposed merge of lp:~jml/testtools/matcher-fixes into lp:testtools.
=== modified file 'NEWS'
--- NEWS 2010-08-02 10:38:54 +0000
+++ NEWS 2010-08-03 17:11:07 +0000
@@ -10,6 +10,12 @@
* jml added a built-in UTF8_TEXT ContentType to make it slightly easier to
add details to test results.
+ * Fix a bug in our built-in matchers where assertThat would blow up if any
+ of them failed. All built-in mismatch objects now provide get_details().
+
+ * New 'Is' matcher, which lets you assert that a thing is identical to
+ another thing.
+
0.9.5
~~~~~
=== modified file 'testtools/matchers.py'
--- testtools/matchers.py 2010-07-29 12:20:37 +0000
+++ testtools/matchers.py 2010-08-03 17:11:07 +0000
@@ -15,6 +15,7 @@
'Annotate',
'DocTestMatches',
'Equals',
+ 'Is',
'MatchesAll',
'MatchesAny',
'NotEquals',
@@ -22,6 +23,7 @@
]
import doctest
+import operator
class Matcher(object):
@@ -120,7 +122,7 @@
return self._checker.output_difference(self, with_nl, self.flags)
-class DocTestMismatch(object):
+class DocTestMismatch(Mismatch):
"""Mismatch object for DocTestMatches."""
def __init__(self, matcher, with_nl):
@@ -131,60 +133,59 @@
return self.matcher._describe_difference(self.with_nl)
-class Equals(object):
+class _BinaryComparison(object):
+ """Matcher that compares an object to another object."""
+
+ def __init__(self, expected):
+ self.expected = expected
+
+ def __str__(self):
+ return "%s(%r)" % (self.__class__.__name__, self.expected)
+
+ def match(self, other):
+ if self.comparator(self.expected, other):
+ return None
+ return _BinaryMismatch(self.expected, self.mismatch_string, other)
+
+ def comparator(self, expected, other):
+ raise NotImplementedError(self.comparator)
+
+
+class _BinaryMismatch(Mismatch):
+ """Two things did not match."""
+
+ def __init__(self, expected, mismatch_string, other):
+ self.expected = expected
+ self._mismatch_string = mismatch_string
+ self.other = other
+
+ def describe(self):
+ return "%r %s %r" % (self.expected, self._mismatch_string, self.other)
+
+
+class Equals(_BinaryComparison):
"""Matches if the items are equal."""
- def __init__(self, expected):
- self.expected = expected
-
- def match(self, other):
- if self.expected == other:
- return None
- return EqualsMismatch(self.expected, other)
-
- def __str__(self):
- return "Equals(%r)" % self.expected
-
-
-class EqualsMismatch(object):
- """Two things differed."""
-
- def __init__(self, expected, other):
- self.expected = expected
- self.other = other
-
- def describe(self):
- return "%r != %r" % (self.expected, self.other)
-
-
-class NotEquals(object):
+ comparator = operator.eq
+ mismatch_string = '!='
+
+
+class NotEquals(_BinaryComparison):
"""Matches if the items are not equal.
In most cases, this is equivalent to `Not(Equals(foo))`. The difference
only matters when testing `__ne__` implementations.
"""
- def __init__(self, expected):
- self.expected = expected
-
- def __str__(self):
- return 'NotEquals(%r)' % (self.expected,)
-
- def match(self, other):
- if self.expected != other:
- return None
- return NotEqualsMismatch(self.expected, other)
-
-
-class NotEqualsMismatch(object):
- """Two things are the same."""
-
- def __init__(self, expected, other):
- self.expected = expected
- self.other = other
-
- def describe(self):
- return '%r == %r' % (self.expected, self.other)
+ comparator = operator.ne
+ mismatch_string = '=='
+
+
+class Is(_BinaryComparison):
+ """Matches if the items are identical."""
+
+ comparator = operator.is_
+ mismatch_string = 'is not'
class MatchesAny(object):
@@ -228,7 +229,7 @@
return None
-class MismatchesAll(object):
+class MismatchesAll(Mismatch):
"""A mismatch with many child mismatches."""
def __init__(self, mismatches):
@@ -259,7 +260,7 @@
return None
-class MatchedUnexpectedly(object):
+class MatchedUnexpectedly(Mismatch):
"""A thing matched when it wasn't supposed to."""
def __init__(self, matcher, other):
@@ -289,7 +290,7 @@
return AnnotatedMismatch(self.annotation, mismatch)
-class AnnotatedMismatch(object):
+class AnnotatedMismatch(Mismatch):
"""A mismatch annotated with a descriptive string."""
def __init__(self, annotation, mismatch):
=== modified file 'testtools/tests/test_matchers.py'
--- testtools/tests/test_matchers.py 2010-07-29 12:20:37 +0000
+++ testtools/tests/test_matchers.py 2010-08-03 17:11:07 +0000
@@ -12,12 +12,16 @@
Annotate,
Equals,
DocTestMatches,
+ Is,
MatchesAny,
MatchesAll,
Not,
NotEquals,
)
+# Silence pyflakes.
+Matcher
+
class TestMatchersInterface(object):
@@ -45,6 +49,15 @@
mismatch = matcher.match(matchee)
self.assertEqual(difference, mismatch.describe())
+ def test_mismatch_details(self):
+ # The mismatch object must provide get_details, which must return a
+ # dictionary mapping names to Content objects.
+ examples = self.describe_examples
+ for difference, matchee, matcher in examples:
+ mismatch = matcher.match(matchee)
+ details = mismatch.get_details()
+ self.assertEqual(dict(details), details)
+
class TestDocTestMatchesInterface(TestCase, TestMatchersInterface):
@@ -97,6 +110,20 @@
describe_examples = [("1 == 1", 1, NotEquals(1))]
+class TestIsInterface(TestCase, TestMatchersInterface):
+
+ foo = object()
+ bar = object()
+
+ matches_matcher = Is(foo)
+ matches_matches = [foo]
+ matches_mismatches = [bar, 1]
+
+ str_examples = [("Is(2)", Is(2))]
+
+ describe_examples = [("1 is not 2", 2, Is(1))]
+
+
class TestNotInterface(TestCase, TestMatchersInterface):
matches_matcher = Not(Equals(1))
Follow ups