Merge lp:~lifeless/testtools/matchers into lp:~testtools-committers/testtools/trunk
- matchers
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 226 |
Proposed branch: | lp:~lifeless/testtools/matchers |
Merge into: | lp:~testtools-committers/testtools/trunk |
Diff against target: |
602 lines (+219/-65) 6 files modified
NEWS (+8/-0) doc/for-test-authors.rst (+26/-0) testtools/matchers.py (+84/-4) testtools/testcase.py (+38/-37) testtools/tests/test_matchers.py (+36/-0) testtools/tests/test_testcase.py (+27/-24) |
To merge this branch: | bzr merge lp:~lifeless/testtools/matchers |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jonathan Lange | Needs Fixing | ||
Review via email: mp+71477@code.launchpad.net |
Commit message
Description of the change
Migrate all the implementations of assert* to be matcher based. Add additional matchers (and tweak existing ones [compatibly] as needed).
Also changed assertThat to take a message; we haven't done a release with verbose= yet, so I put the parameter in in the 'natural' place.
Robert Collins (lifeless) wrote : | # |
On Mon, Aug 15, 2011 at 11:44 PM, Jonathan Lange <email address hidden> wrote:
> Review: Needs Fixing
> Thanks for doing this.
>
> doc/for-
> * The heading for the IsInstance matcher is "Is", should be "IsInstance".
>
> testtools/
> * Please delete the XXX in assertThat about taking an optional message parameter.
> * The line "raise matchee[0], matchee[1], matchee[2]" is invalid syntax in Python 3. Use testtools.
The rest seem shallow; I will get to them at some point(ETOOMUCHON),
perhaps you would like to just fix-as-landing ?
> * Is there a reason that expectFailure wasn't also changed?
flippantly, it didn't have a TODO; more seriously, it was complex, and
the branch was big enough, and ugly enough, that I wanted to wrap it
up.
> Generally, the new failure messages for the negative assert methods (e.g. assertNotIn) are worse than the current ones. To some extent, this would be addressed by a fix for bug 704219.
What should we do about this?
> Separately from this branch, I think we want to provide an In or IsIn matcher to complement Contains.
Yes.
-Rob
Jonathan Lange (jml) wrote : | # |
On Mon, Aug 15, 2011 at 1:01 PM, Robert Collins
<email address hidden> wrote:
> On Mon, Aug 15, 2011 at 11:44 PM, Jonathan Lange <email address hidden> wrote:
>> Review: Needs Fixing
>> Thanks for doing this.
>>
>> doc/for-
>> * The heading for the IsInstance matcher is "Is", should be "IsInstance".
>>
>> testtools/
>> * Please delete the XXX in assertThat about taking an optional message parameter.
>> * The line "raise matchee[0], matchee[1], matchee[2]" is invalid syntax in Python 3. Use testtools.
>
> The rest seem shallow; I will get to them at some point(ETOOMUCHON),
> perhaps you would like to just fix-as-landing ?
>
Am happy to do so.
>> * Is there a reason that expectFailure wasn't also changed?
>
> flippantly, it didn't have a TODO; more seriously, it was complex, and
> the branch was big enough, and ugly enough, that I wanted to wrap it
> up.
>
Fair enough.
>> Generally, the new failure messages for the negative assert methods (e.g. assertNotIn) are worse than the current ones. To some extent, this would be addressed by a fix for bug 704219.
>
> What should we do about this?
>
Fix the bug, later.
jml
Preview Diff
1 | === modified file 'NEWS' |
2 | --- NEWS 2011-08-09 13:12:39 +0000 |
3 | +++ NEWS 2011-08-14 12:18:23 +0000 |
4 | @@ -34,6 +34,9 @@ |
5 | tells us to display. Old-style verbose output can be had by passing |
6 | ``verbose=True`` to assertThat. (Jonathan Lange, #675323, #593190) |
7 | |
8 | +* assertThat accepts a message which will be used to annotate the matcher. This |
9 | + can be given as a third parameter or as a keyword parameter. (Robert Collins) |
10 | + |
11 | * Automated the Launchpad part of the release process. |
12 | (Jonathan Lange, #623486) |
13 | |
14 | @@ -62,6 +65,9 @@ |
15 | * ``MatchesException`` now allows you to match exceptions against any matcher, |
16 | rather than just regular expressions. (Jonathan Lange, #791889) |
17 | |
18 | +* ``MatchesException`` now permits a tuple of types rather than a single type |
19 | + (when using the type matching mode). (Robert Collins) |
20 | + |
21 | * ``MatchesStructure.byEquality`` added to make the common case of matching |
22 | many attributes by equality much easier. ``MatchesStructure.byMatcher`` |
23 | added in case folk want to match by things other than equality. |
24 | @@ -75,6 +81,8 @@ |
25 | * ``AllMatch`` matches many values against a single matcher. |
26 | (Jonathan Lange, #615108) |
27 | |
28 | + * ``Contains``. (Robert Collins) |
29 | + |
30 | * ``GreaterThan``. (Christian Kampka) |
31 | |
32 | * New helper, ``safe_hasattr`` added. (Jonathan Lange) |
33 | |
34 | === modified file 'doc/for-test-authors.rst' |
35 | --- doc/for-test-authors.rst 2011-07-27 20:21:53 +0000 |
36 | +++ doc/for-test-authors.rst 2011-08-14 12:18:23 +0000 |
37 | @@ -312,6 +312,17 @@ |
38 | self.assertThat(foo, Is(foo)) |
39 | |
40 | |
41 | +Is |
42 | +~~~ |
43 | + |
44 | +Adapts isinstance() to use as a matcher. For example:: |
45 | + |
46 | + def test_isinstance_example(self): |
47 | + class MyClass:pass |
48 | + self.assertThat(MyClass(), IsInstance(MyClass)) |
49 | + self.assertThat(MyClass(), IsInstance(MyClass, str)) |
50 | + |
51 | + |
52 | The raises helper |
53 | ~~~~~~~~~~~~~~~~~ |
54 | |
55 | @@ -374,6 +385,16 @@ |
56 | self.assertThat('underground', EndsWith('und')) |
57 | |
58 | |
59 | +Contains |
60 | +~~~~~~~~ |
61 | + |
62 | +This matcher checks to see if the given thing contains the thing in the |
63 | +matcher. For example:: |
64 | + |
65 | + def test_contains_example(self): |
66 | + self.assertThat('abc', Contains('b')) |
67 | + |
68 | + |
69 | MatchesException |
70 | ~~~~~~~~~~~~~~~~ |
71 | |
72 | @@ -474,6 +495,11 @@ |
73 | def test_annotate_example_2(self): |
74 | self.assertThat("orange", PoliticallyEquals("yellow")) |
75 | |
76 | +You can have assertThat perform the annotation for you as a convenience:: |
77 | + |
78 | + def test_annotate_example_3(self): |
79 | + self.assertThat("orange", Equals("yellow"), "Death to the aristos!") |
80 | + |
81 | |
82 | AfterPreprocessing |
83 | ~~~~~~~~~~~~~~~~~~ |
84 | |
85 | === modified file 'testtools/matchers.py' |
86 | --- testtools/matchers.py 2011-08-08 11:14:01 +0000 |
87 | +++ testtools/matchers.py 2011-08-14 12:18:23 +0000 |
88 | @@ -15,11 +15,13 @@ |
89 | 'AfterPreprocessing', |
90 | 'AllMatch', |
91 | 'Annotate', |
92 | + 'Contains', |
93 | 'DocTestMatches', |
94 | 'EndsWith', |
95 | 'Equals', |
96 | 'GreaterThan', |
97 | 'Is', |
98 | + 'IsInstance', |
99 | 'KeysEqual', |
100 | 'LessThan', |
101 | 'MatchesAll', |
102 | @@ -242,6 +244,21 @@ |
103 | return self.matcher._describe_difference(self.with_nl) |
104 | |
105 | |
106 | +class DoesNotContain(Mismatch): |
107 | + |
108 | + def __init__(self, matchee, needle): |
109 | + """Create a DoesNotContain Mismatch. |
110 | + |
111 | + :param matchee: the object that did not contain needle. |
112 | + :param needle: the needle that 'matchee' was expected to contain. |
113 | + """ |
114 | + self.matchee = matchee |
115 | + self.needle = needle |
116 | + |
117 | + def describe(self): |
118 | + return "%r not present in %r" % (self.needle, self.matchee) |
119 | + |
120 | + |
121 | class DoesNotStartWith(Mismatch): |
122 | |
123 | def __init__(self, matchee, expected): |
124 | @@ -343,6 +360,42 @@ |
125 | mismatch_string = 'is not' |
126 | |
127 | |
128 | +class IsInstance(object): |
129 | + """Matcher that wraps isinstance.""" |
130 | + |
131 | + def __init__(self, *types): |
132 | + self.types = tuple(types) |
133 | + |
134 | + def __str__(self): |
135 | + return "%s(%s)" % (self.__class__.__name__, |
136 | + ', '.join(type.__name__ for type in self.types)) |
137 | + |
138 | + def match(self, other): |
139 | + if isinstance(other, self.types): |
140 | + return None |
141 | + return NotAnInstance(other, self.types) |
142 | + |
143 | + |
144 | +class NotAnInstance(Mismatch): |
145 | + |
146 | + def __init__(self, matchee, types): |
147 | + """Create a NotAnInstance Mismatch. |
148 | + |
149 | + :param matchee: the thing which is not an instance of any of types. |
150 | + :param types: A tuple of the types which were expected. |
151 | + """ |
152 | + self.matchee = matchee |
153 | + self.types = types |
154 | + |
155 | + def describe(self): |
156 | + if len(self.types) == 1: |
157 | + typestr = self.types[0].__name__ |
158 | + else: |
159 | + typestr = 'any of (%s)' % ', '.join(type.__name__ for type in |
160 | + self.types) |
161 | + return "'%s' is not an instance of %s" % (self.matchee, typestr) |
162 | + |
163 | + |
164 | class LessThan(_BinaryComparison): |
165 | """Matches if the item is less than the matchers reference object.""" |
166 | |
167 | @@ -449,7 +502,8 @@ |
168 | :param exception: Either an exception instance or type. |
169 | If an instance is given, the type and arguments of the exception |
170 | are checked. If a type is given only the type of the exception is |
171 | - checked. |
172 | + checked. If a tuple is given, then as with isinstance, any of the |
173 | + types in the tuple matching is sufficient to match. |
174 | :param value_re: If 'exception' is a type, and the matchee exception |
175 | is of the right type, then match against this. If value_re is a |
176 | string, then assume value_re is a regular expression and match |
177 | @@ -461,7 +515,7 @@ |
178 | if istext(value_re): |
179 | value_re = AfterPreproccessing(str, MatchesRegex(value_re), False) |
180 | self.value_re = value_re |
181 | - self._is_instance = type(self.expected) not in classtypes() |
182 | + self._is_instance = type(self.expected) not in classtypes() + (tuple,) |
183 | |
184 | def match(self, other): |
185 | if type(other) != tuple: |
186 | @@ -484,6 +538,29 @@ |
187 | return "MatchesException(%s)" % repr(self.expected) |
188 | |
189 | |
190 | +class Contains(Matcher): |
191 | + """Checks whether something is container in another thing.""" |
192 | + |
193 | + def __init__(self, needle): |
194 | + """Create a Contains Matcher. |
195 | + |
196 | + :param needle: the thing that needs to be contained by matchees. |
197 | + """ |
198 | + self.needle = needle |
199 | + |
200 | + def __str__(self): |
201 | + return "Contains(%r)" % (self.needle,) |
202 | + |
203 | + def match(self, matchee): |
204 | + try: |
205 | + if self.needle not in matchee: |
206 | + return DoesNotContain(matchee, self.needle) |
207 | + except TypeError: |
208 | + # e.g. 1 in 2 will raise TypeError |
209 | + return DoesNotContain(matchee, self.needle) |
210 | + return None |
211 | + |
212 | + |
213 | class StartsWith(Matcher): |
214 | """Checks whether one string starts with another.""" |
215 | |
216 | @@ -613,16 +690,19 @@ |
217 | # Catch all exceptions: Raises() should be able to match a |
218 | # KeyboardInterrupt or SystemExit. |
219 | except: |
220 | + exc_info = sys.exc_info() |
221 | if self.exception_matcher: |
222 | - mismatch = self.exception_matcher.match(sys.exc_info()) |
223 | + mismatch = self.exception_matcher.match(exc_info) |
224 | if not mismatch: |
225 | + del exc_info |
226 | return |
227 | else: |
228 | mismatch = None |
229 | # The exception did not match, or no explicit matching logic was |
230 | # performed. If the exception is a non-user exception (that is, not |
231 | # a subclass of Exception on Python 2.5+) then propogate it. |
232 | - if isbaseexception(sys.exc_info()[1]): |
233 | + if isbaseexception(exc_info[1]): |
234 | + del exc_info |
235 | raise |
236 | return mismatch |
237 | |
238 | |
239 | === modified file 'testtools/testcase.py' |
240 | --- testtools/testcase.py 2011-07-27 19:47:22 +0000 |
241 | +++ testtools/testcase.py 2011-08-14 12:18:23 +0000 |
242 | @@ -27,10 +27,14 @@ |
243 | from testtools.compat import advance_iterator |
244 | from testtools.matchers import ( |
245 | Annotate, |
246 | + Contains, |
247 | Equals, |
248 | + MatchesAll, |
249 | MatchesException, |
250 | Is, |
251 | + IsInstance, |
252 | Not, |
253 | + Raises, |
254 | ) |
255 | from testtools.monkey import patch |
256 | from testtools.runtest import RunTest |
257 | @@ -304,16 +308,14 @@ |
258 | :param observed: The observed value. |
259 | :param message: An optional message to include in the error. |
260 | """ |
261 | - matcher = Annotate.if_message(message, Equals(expected)) |
262 | - self.assertThat(observed, matcher) |
263 | + matcher = Equals(expected) |
264 | + self.assertThat(observed, matcher, message) |
265 | |
266 | failUnlessEqual = assertEquals = assertEqual |
267 | |
268 | def assertIn(self, needle, haystack): |
269 | """Assert that needle is in haystack.""" |
270 | - # XXX: Re-implement with matchers. |
271 | - if needle not in haystack: |
272 | - self.fail('%r not in %r' % (needle, haystack)) |
273 | + self.assertThat(haystack, Contains(needle)) |
274 | |
275 | def assertIsNone(self, observed, message=''): |
276 | """Assert that 'observed' is equal to None. |
277 | @@ -321,8 +323,8 @@ |
278 | :param observed: The observed value. |
279 | :param message: An optional message describing the error. |
280 | """ |
281 | - matcher = Annotate.if_message(message, Is(None)) |
282 | - self.assertThat(observed, matcher) |
283 | + matcher = Is(None) |
284 | + self.assertThat(observed, matcher, message) |
285 | |
286 | def assertIsNotNone(self, observed, message=''): |
287 | """Assert that 'observed' is not equal to None. |
288 | @@ -330,8 +332,8 @@ |
289 | :param observed: The observed value. |
290 | :param message: An optional message describing the error. |
291 | """ |
292 | - matcher = Annotate.if_message(message, Not(Is(None))) |
293 | - self.assertThat(observed, matcher) |
294 | + matcher = Not(Is(None)) |
295 | + self.assertThat(observed, matcher, message) |
296 | |
297 | def assertIs(self, expected, observed, message=''): |
298 | """Assert that 'expected' is 'observed'. |
299 | @@ -340,33 +342,25 @@ |
300 | :param observed: The observed value. |
301 | :param message: An optional message describing the error. |
302 | """ |
303 | - # XXX: Re-implement with matchers. |
304 | - if message: |
305 | - message = ': ' + message |
306 | - if expected is not observed: |
307 | - self.fail('%r is not %r%s' % (expected, observed, message)) |
308 | + matcher = Is(expected) |
309 | + self.assertThat(observed, matcher, message) |
310 | |
311 | def assertIsNot(self, expected, observed, message=''): |
312 | """Assert that 'expected' is not 'observed'.""" |
313 | - # XXX: Re-implement with matchers. |
314 | - if message: |
315 | - message = ': ' + message |
316 | - if expected is observed: |
317 | - self.fail('%r is %r%s' % (expected, observed, message)) |
318 | + matcher = Not(Is(expected)) |
319 | + self.assertThat(observed, matcher, message) |
320 | |
321 | def assertNotIn(self, needle, haystack): |
322 | """Assert that needle is not in haystack.""" |
323 | - # XXX: Re-implement with matchers. |
324 | - if needle in haystack: |
325 | - self.fail('%r in %r' % (needle, haystack)) |
326 | + matcher = Not(Contains(needle)) |
327 | + self.assertThat(haystack, matcher) |
328 | |
329 | def assertIsInstance(self, obj, klass, msg=None): |
330 | - # XXX: Re-implement with matchers. |
331 | - if msg is None: |
332 | - msg = '%r is not an instance of %s' % ( |
333 | - obj, self._formatTypes(klass)) |
334 | - if not isinstance(obj, klass): |
335 | - self.fail(msg) |
336 | + if isinstance(klass, tuple): |
337 | + matcher = IsInstance(*klass) |
338 | + else: |
339 | + matcher = IsInstance(klass) |
340 | + self.assertThat(obj, matcher, msg) |
341 | |
342 | def assertRaises(self, excClass, callableObj, *args, **kwargs): |
343 | """Fail unless an exception of class excClass is thrown |
344 | @@ -376,17 +370,22 @@ |
345 | deemed to have suffered an error, exactly as for an |
346 | unexpected exception. |
347 | """ |
348 | - # XXX: Re-implement with matchers. |
349 | - try: |
350 | - ret = callableObj(*args, **kwargs) |
351 | - except excClass: |
352 | - return sys.exc_info()[1] |
353 | - else: |
354 | - excName = self._formatTypes(excClass) |
355 | - self.fail("%s not raised, %r returned instead." % (excName, ret)) |
356 | + class ReRaiseOtherTypes(object): |
357 | + def match(self, matchee): |
358 | + if not issubclass(matchee[0], excClass): |
359 | + raise matchee[0], matchee[1], matchee[2] |
360 | + class CaptureMatchee(object): |
361 | + def match(self, matchee): |
362 | + self.matchee = matchee[1] |
363 | + capture = CaptureMatchee() |
364 | + matcher = Raises(MatchesAll(ReRaiseOtherTypes(), |
365 | + MatchesException(excClass), capture)) |
366 | + |
367 | + self.assertThat(lambda:callableObj(*args, **kwargs), matcher) |
368 | + return capture.matchee |
369 | failUnlessRaises = assertRaises |
370 | |
371 | - def assertThat(self, matchee, matcher, verbose=False): |
372 | + def assertThat(self, matchee, matcher, message='', verbose=False): |
373 | """Assert that matchee is matched by matcher. |
374 | |
375 | :param matchee: An object to match with matcher. |
376 | @@ -395,6 +394,7 @@ |
377 | """ |
378 | # XXX: Should this take an optional 'message' parameter? Would kind of |
379 | # make sense. The hamcrest one does. |
380 | + matcher = Annotate.if_message(message, matcher) |
381 | mismatch = matcher.match(matchee) |
382 | if not mismatch: |
383 | return |
384 | @@ -433,6 +433,7 @@ |
385 | be removed. This separation preserves the original intent of the test |
386 | while it is in the expectFailure mode. |
387 | """ |
388 | + # TODO: implement with matchers. |
389 | self._add_reason(reason) |
390 | try: |
391 | predicate(*args, **kwargs) |
392 | |
393 | === modified file 'testtools/tests/test_matchers.py' |
394 | --- testtools/tests/test_matchers.py 2011-08-08 11:14:01 +0000 |
395 | +++ testtools/tests/test_matchers.py 2011-08-14 12:18:23 +0000 |
396 | @@ -19,6 +19,7 @@ |
397 | AllMatch, |
398 | Annotate, |
399 | AnnotatedMismatch, |
400 | + Contains, |
401 | Equals, |
402 | DocTestMatches, |
403 | DoesNotEndWith, |
404 | @@ -26,6 +27,7 @@ |
405 | EndsWith, |
406 | KeysEqual, |
407 | Is, |
408 | + IsInstance, |
409 | LessThan, |
410 | GreaterThan, |
411 | MatchesAny, |
412 | @@ -179,6 +181,26 @@ |
413 | describe_examples = [("1 is not 2", 2, Is(1))] |
414 | |
415 | |
416 | +class TestIsInstanceInterface(TestCase, TestMatchersInterface): |
417 | + |
418 | + class Foo:pass |
419 | + |
420 | + matches_matcher = IsInstance(Foo) |
421 | + matches_matches = [Foo()] |
422 | + matches_mismatches = [object(), 1, Foo] |
423 | + |
424 | + str_examples = [ |
425 | + ("IsInstance(str)", IsInstance(str)), |
426 | + ("IsInstance(str, int)", IsInstance(str, int)), |
427 | + ] |
428 | + |
429 | + describe_examples = [ |
430 | + ("'foo' is not an instance of int", 'foo', IsInstance(int)), |
431 | + ("'foo' is not an instance of any of (int, type)", 'foo', |
432 | + IsInstance(int, type)), |
433 | + ] |
434 | + |
435 | + |
436 | class TestLessThanInterface(TestCase, TestMatchersInterface): |
437 | |
438 | matches_matcher = LessThan(4) |
439 | @@ -211,6 +233,20 @@ |
440 | ] |
441 | |
442 | |
443 | +class TestContainsInterface(TestCase, TestMatchersInterface): |
444 | + |
445 | + matches_matcher = Contains('foo') |
446 | + matches_matches = ['foo', 'afoo', 'fooa'] |
447 | + matches_mismatches = ['f', 'fo', 'oo', 'faoo', 'foao'] |
448 | + |
449 | + str_examples = [ |
450 | + ("Contains(1)", Contains(1)), |
451 | + ("Contains('foo')", Contains('foo')), |
452 | + ] |
453 | + |
454 | + describe_examples = [("1 not present in 2", 2, Contains(1))] |
455 | + |
456 | + |
457 | def make_error(type, *args, **kwargs): |
458 | try: |
459 | raise type(*args, **kwargs) |
460 | |
461 | === modified file 'testtools/tests/test_testcase.py' |
462 | --- testtools/tests/test_testcase.py 2011-07-26 23:48:48 +0000 |
463 | +++ testtools/tests/test_testcase.py 2011-08-14 12:18:23 +0000 |
464 | @@ -2,6 +2,7 @@ |
465 | |
466 | """Tests for extensions to the base test library.""" |
467 | |
468 | +from doctest import ELLIPSIS |
469 | from pprint import pformat |
470 | import sys |
471 | import unittest |
472 | @@ -20,6 +21,8 @@ |
473 | ) |
474 | from testtools.compat import _b |
475 | from testtools.matchers import ( |
476 | + Annotate, |
477 | + DocTestMatches, |
478 | Equals, |
479 | MatchesException, |
480 | Raises, |
481 | @@ -244,16 +247,8 @@ |
482 | # assertRaises raises self.failureException when it's passed a |
483 | # callable that raises no error. |
484 | ret = ('orange', 42) |
485 | - try: |
486 | - self.assertRaises(RuntimeError, lambda: ret) |
487 | - except self.failureException: |
488 | - # We expected assertRaises to raise this exception. |
489 | - e = sys.exc_info()[1] |
490 | - self.assertEqual( |
491 | - '%s not raised, %r returned instead.' |
492 | - % (self._formatTypes(RuntimeError), ret), str(e)) |
493 | - else: |
494 | - self.fail('Expected assertRaises to fail, but it did not.') |
495 | + self.assertFails("<function <lambda> at ...> returned ('orange', 42)", |
496 | + self.assertRaises, RuntimeError, lambda: ret) |
497 | |
498 | def test_assertRaises_fails_when_different_error_raised(self): |
499 | # assertRaises re-raises an exception that it didn't expect. |
500 | @@ -298,15 +293,14 @@ |
501 | failure = self.assertRaises( |
502 | self.failureException, |
503 | self.assertRaises, expectedExceptions, lambda: None) |
504 | - self.assertEqual( |
505 | - '%s not raised, None returned instead.' |
506 | - % self._formatTypes(expectedExceptions), str(failure)) |
507 | + self.assertFails('<function <lambda> at ...> returned None', |
508 | + self.assertRaises, expectedExceptions, lambda: None) |
509 | |
510 | def assertFails(self, message, function, *args, **kwargs): |
511 | """Assert that function raises a failure with the given message.""" |
512 | failure = self.assertRaises( |
513 | self.failureException, function, *args, **kwargs) |
514 | - self.assertEqual(message, str(failure)) |
515 | + self.assertThat(failure, DocTestMatches(message, ELLIPSIS)) |
516 | |
517 | def test_assertIn_success(self): |
518 | # assertIn(needle, haystack) asserts that 'needle' is in 'haystack'. |
519 | @@ -317,9 +311,9 @@ |
520 | def test_assertIn_failure(self): |
521 | # assertIn(needle, haystack) fails the test when 'needle' is not in |
522 | # 'haystack'. |
523 | - self.assertFails('3 not in [0, 1, 2]', self.assertIn, 3, [0, 1, 2]) |
524 | + self.assertFails('3 not present in [0, 1, 2]', self.assertIn, 3, [0, 1, 2]) |
525 | self.assertFails( |
526 | - '%r not in %r' % ('qux', 'foo bar baz'), |
527 | + '%r not present in %r' % ('qux', 'foo bar baz'), |
528 | self.assertIn, 'qux', 'foo bar baz') |
529 | |
530 | def test_assertNotIn_success(self): |
531 | @@ -331,9 +325,10 @@ |
532 | def test_assertNotIn_failure(self): |
533 | # assertNotIn(needle, haystack) fails the test when 'needle' is in |
534 | # 'haystack'. |
535 | - self.assertFails('3 in [1, 2, 3]', self.assertNotIn, 3, [1, 2, 3]) |
536 | + self.assertFails('[1, 2, 3] matches Contains(3)', self.assertNotIn, |
537 | + 3, [1, 2, 3]) |
538 | self.assertFails( |
539 | - '%r in %r' % ('foo', 'foo bar baz'), |
540 | + "'foo bar baz' matches Contains('foo')", |
541 | self.assertNotIn, 'foo', 'foo bar baz') |
542 | |
543 | def test_assertIsInstance(self): |
544 | @@ -367,7 +362,7 @@ |
545 | """Simple class for testing assertIsInstance.""" |
546 | |
547 | self.assertFails( |
548 | - '42 is not an instance of %s' % self._formatTypes(Foo), |
549 | + "'42' is not an instance of %s" % self._formatTypes(Foo), |
550 | self.assertIsInstance, 42, Foo) |
551 | |
552 | def test_assertIsInstance_failure_multiple_classes(self): |
553 | @@ -381,12 +376,13 @@ |
554 | """Another simple class for testing assertIsInstance.""" |
555 | |
556 | self.assertFails( |
557 | - '42 is not an instance of %s' % self._formatTypes([Foo, Bar]), |
558 | + "'42' is not an instance of any of (%s)" % self._formatTypes([Foo, Bar]), |
559 | self.assertIsInstance, 42, (Foo, Bar)) |
560 | |
561 | def test_assertIsInstance_overridden_message(self): |
562 | # assertIsInstance(obj, klass, msg) permits a custom message. |
563 | - self.assertFails("foo", self.assertIsInstance, 42, str, "foo") |
564 | + self.assertFails("'42' is not an instance of str: foo", |
565 | + self.assertIsInstance, 42, str, "foo") |
566 | |
567 | def test_assertIs(self): |
568 | # assertIs asserts that an object is identical to another object. |
569 | @@ -418,16 +414,17 @@ |
570 | def test_assertIsNot_fails(self): |
571 | # assertIsNot raises assertion errors if one object is identical to |
572 | # another. |
573 | - self.assertFails('None is None', self.assertIsNot, None, None) |
574 | + self.assertFails('None matches Is(None)', self.assertIsNot, None, None) |
575 | some_list = [42] |
576 | self.assertFails( |
577 | - '[42] is [42]', self.assertIsNot, some_list, some_list) |
578 | + '[42] matches Is([42])', self.assertIsNot, some_list, some_list) |
579 | |
580 | def test_assertIsNot_fails_with_message(self): |
581 | # assertIsNot raises assertion errors if one object is identical to |
582 | # another, and includes a user-supplied message if it's provided. |
583 | self.assertFails( |
584 | - 'None is None: foo bar', self.assertIsNot, None, None, "foo bar") |
585 | + 'None matches Is(None): foo bar', self.assertIsNot, None, None, |
586 | + "foo bar") |
587 | |
588 | def test_assertThat_matches_clean(self): |
589 | class Matcher(object): |
590 | @@ -468,6 +465,12 @@ |
591 | expected = matcher.match(matchee).describe() |
592 | self.assertFails(expected, self.assertThat, matchee, matcher) |
593 | |
594 | + def test_assertThat_message_is_annotated(self): |
595 | + matchee = 'foo' |
596 | + matcher = Equals('bar') |
597 | + expected = Annotate('woo', matcher).match(matchee).describe() |
598 | + self.assertFails(expected, self.assertThat, matchee, matcher, 'woo') |
599 | + |
600 | def test_assertThat_verbose_output(self): |
601 | matchee = 'foo' |
602 | matcher = Equals('bar') |
Thanks for doing this.
doc/for- test-authors. rst:
* The heading for the IsInstance matcher is "Is", should be "IsInstance".
testtools/ testcase. py compat. reraise( *matchee) instead.
* Please delete the XXX in assertThat about taking an optional message parameter.
* The line "raise matchee[0], matchee[1], matchee[2]" is invalid syntax in Python 3. Use testtools.
* Is there a reason that expectFailure wasn't also changed?
Generally, the new failure messages for the negative assert methods (e.g. assertNotIn) are worse than the current ones. To some extent, this would be addressed by a fix for bug 704219.
Separately from this branch, I think we want to provide an In or IsIn matcher to complement Contains.