Merge lp:~lifeless/testtools/matchers into lp:~testtools-committers/testtools/trunk

Proposed by Robert Collins
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
Reviewer Review Type Date Requested Status
Jonathan Lange Needs Fixing
Review via email: mp+71477@code.launchpad.net

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.

To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote :

Thanks for doing this.

doc/for-test-authors.rst:
 * The heading for the IsInstance matcher is "Is", should be "IsInstance".

testtools/testcase.py
 * 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.compat.reraise(*matchee) instead.
 * 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.

review: Needs Fixing
Revision history for this message
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-test-authors.rst:
>  * The heading for the IsInstance matcher is "Is", should be "IsInstance".
>
> testtools/testcase.py
>  * 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.compat.reraise(*matchee) instead.

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

Revision history for this message
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-test-authors.rst:
>>  * The heading for the IsInstance matcher is "Is", should be "IsInstance".
>>
>> testtools/testcase.py
>>  * 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.compat.reraise(*matchee) instead.
>
> 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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2011-08-09 13:12:39 +0000
+++ NEWS 2011-08-14 12:18:23 +0000
@@ -34,6 +34,9 @@
34 tells us to display. Old-style verbose output can be had by passing34 tells us to display. Old-style verbose output can be had by passing
35 ``verbose=True`` to assertThat. (Jonathan Lange, #675323, #593190)35 ``verbose=True`` to assertThat. (Jonathan Lange, #675323, #593190)
3636
37* assertThat accepts a message which will be used to annotate the matcher. This
38 can be given as a third parameter or as a keyword parameter. (Robert Collins)
39
37* Automated the Launchpad part of the release process.40* Automated the Launchpad part of the release process.
38 (Jonathan Lange, #623486)41 (Jonathan Lange, #623486)
3942
@@ -62,6 +65,9 @@
62* ``MatchesException`` now allows you to match exceptions against any matcher,65* ``MatchesException`` now allows you to match exceptions against any matcher,
63 rather than just regular expressions. (Jonathan Lange, #791889)66 rather than just regular expressions. (Jonathan Lange, #791889)
6467
68* ``MatchesException`` now permits a tuple of types rather than a single type
69 (when using the type matching mode). (Robert Collins)
70
65* ``MatchesStructure.byEquality`` added to make the common case of matching71* ``MatchesStructure.byEquality`` added to make the common case of matching
66 many attributes by equality much easier. ``MatchesStructure.byMatcher``72 many attributes by equality much easier. ``MatchesStructure.byMatcher``
67 added in case folk want to match by things other than equality.73 added in case folk want to match by things other than equality.
@@ -75,6 +81,8 @@
75 * ``AllMatch`` matches many values against a single matcher.81 * ``AllMatch`` matches many values against a single matcher.
76 (Jonathan Lange, #615108)82 (Jonathan Lange, #615108)
7783
84 * ``Contains``. (Robert Collins)
85
78 * ``GreaterThan``. (Christian Kampka)86 * ``GreaterThan``. (Christian Kampka)
7987
80* New helper, ``safe_hasattr`` added. (Jonathan Lange)88* New helper, ``safe_hasattr`` added. (Jonathan Lange)
8189
=== modified file 'doc/for-test-authors.rst'
--- doc/for-test-authors.rst 2011-07-27 20:21:53 +0000
+++ doc/for-test-authors.rst 2011-08-14 12:18:23 +0000
@@ -312,6 +312,17 @@
312 self.assertThat(foo, Is(foo))312 self.assertThat(foo, Is(foo))
313313
314314
315Is
316~~~
317
318Adapts isinstance() to use as a matcher. For example::
319
320 def test_isinstance_example(self):
321 class MyClass:pass
322 self.assertThat(MyClass(), IsInstance(MyClass))
323 self.assertThat(MyClass(), IsInstance(MyClass, str))
324
325
315The raises helper326The raises helper
316~~~~~~~~~~~~~~~~~327~~~~~~~~~~~~~~~~~
317328
@@ -374,6 +385,16 @@
374 self.assertThat('underground', EndsWith('und'))385 self.assertThat('underground', EndsWith('und'))
375386
376387
388Contains
389~~~~~~~~
390
391This matcher checks to see if the given thing contains the thing in the
392matcher. For example::
393
394 def test_contains_example(self):
395 self.assertThat('abc', Contains('b'))
396
397
377MatchesException398MatchesException
378~~~~~~~~~~~~~~~~399~~~~~~~~~~~~~~~~
379400
@@ -474,6 +495,11 @@
474 def test_annotate_example_2(self):495 def test_annotate_example_2(self):
475 self.assertThat("orange", PoliticallyEquals("yellow"))496 self.assertThat("orange", PoliticallyEquals("yellow"))
476497
498You can have assertThat perform the annotation for you as a convenience::
499
500 def test_annotate_example_3(self):
501 self.assertThat("orange", Equals("yellow"), "Death to the aristos!")
502
477503
478AfterPreprocessing504AfterPreprocessing
479~~~~~~~~~~~~~~~~~~505~~~~~~~~~~~~~~~~~~
480506
=== modified file 'testtools/matchers.py'
--- testtools/matchers.py 2011-08-08 11:14:01 +0000
+++ testtools/matchers.py 2011-08-14 12:18:23 +0000
@@ -15,11 +15,13 @@
15 'AfterPreprocessing',15 'AfterPreprocessing',
16 'AllMatch',16 'AllMatch',
17 'Annotate',17 'Annotate',
18 'Contains',
18 'DocTestMatches',19 'DocTestMatches',
19 'EndsWith',20 'EndsWith',
20 'Equals',21 'Equals',
21 'GreaterThan',22 'GreaterThan',
22 'Is',23 'Is',
24 'IsInstance',
23 'KeysEqual',25 'KeysEqual',
24 'LessThan',26 'LessThan',
25 'MatchesAll',27 'MatchesAll',
@@ -242,6 +244,21 @@
242 return self.matcher._describe_difference(self.with_nl)244 return self.matcher._describe_difference(self.with_nl)
243245
244246
247class DoesNotContain(Mismatch):
248
249 def __init__(self, matchee, needle):
250 """Create a DoesNotContain Mismatch.
251
252 :param matchee: the object that did not contain needle.
253 :param needle: the needle that 'matchee' was expected to contain.
254 """
255 self.matchee = matchee
256 self.needle = needle
257
258 def describe(self):
259 return "%r not present in %r" % (self.needle, self.matchee)
260
261
245class DoesNotStartWith(Mismatch):262class DoesNotStartWith(Mismatch):
246263
247 def __init__(self, matchee, expected):264 def __init__(self, matchee, expected):
@@ -343,6 +360,42 @@
343 mismatch_string = 'is not'360 mismatch_string = 'is not'
344361
345362
363class IsInstance(object):
364 """Matcher that wraps isinstance."""
365
366 def __init__(self, *types):
367 self.types = tuple(types)
368
369 def __str__(self):
370 return "%s(%s)" % (self.__class__.__name__,
371 ', '.join(type.__name__ for type in self.types))
372
373 def match(self, other):
374 if isinstance(other, self.types):
375 return None
376 return NotAnInstance(other, self.types)
377
378
379class NotAnInstance(Mismatch):
380
381 def __init__(self, matchee, types):
382 """Create a NotAnInstance Mismatch.
383
384 :param matchee: the thing which is not an instance of any of types.
385 :param types: A tuple of the types which were expected.
386 """
387 self.matchee = matchee
388 self.types = types
389
390 def describe(self):
391 if len(self.types) == 1:
392 typestr = self.types[0].__name__
393 else:
394 typestr = 'any of (%s)' % ', '.join(type.__name__ for type in
395 self.types)
396 return "'%s' is not an instance of %s" % (self.matchee, typestr)
397
398
346class LessThan(_BinaryComparison):399class LessThan(_BinaryComparison):
347 """Matches if the item is less than the matchers reference object."""400 """Matches if the item is less than the matchers reference object."""
348401
@@ -449,7 +502,8 @@
449 :param exception: Either an exception instance or type.502 :param exception: Either an exception instance or type.
450 If an instance is given, the type and arguments of the exception503 If an instance is given, the type and arguments of the exception
451 are checked. If a type is given only the type of the exception is504 are checked. If a type is given only the type of the exception is
452 checked.505 checked. If a tuple is given, then as with isinstance, any of the
506 types in the tuple matching is sufficient to match.
453 :param value_re: If 'exception' is a type, and the matchee exception507 :param value_re: If 'exception' is a type, and the matchee exception
454 is of the right type, then match against this. If value_re is a508 is of the right type, then match against this. If value_re is a
455 string, then assume value_re is a regular expression and match509 string, then assume value_re is a regular expression and match
@@ -461,7 +515,7 @@
461 if istext(value_re):515 if istext(value_re):
462 value_re = AfterPreproccessing(str, MatchesRegex(value_re), False)516 value_re = AfterPreproccessing(str, MatchesRegex(value_re), False)
463 self.value_re = value_re517 self.value_re = value_re
464 self._is_instance = type(self.expected) not in classtypes()518 self._is_instance = type(self.expected) not in classtypes() + (tuple,)
465519
466 def match(self, other):520 def match(self, other):
467 if type(other) != tuple:521 if type(other) != tuple:
@@ -484,6 +538,29 @@
484 return "MatchesException(%s)" % repr(self.expected)538 return "MatchesException(%s)" % repr(self.expected)
485539
486540
541class Contains(Matcher):
542 """Checks whether something is container in another thing."""
543
544 def __init__(self, needle):
545 """Create a Contains Matcher.
546
547 :param needle: the thing that needs to be contained by matchees.
548 """
549 self.needle = needle
550
551 def __str__(self):
552 return "Contains(%r)" % (self.needle,)
553
554 def match(self, matchee):
555 try:
556 if self.needle not in matchee:
557 return DoesNotContain(matchee, self.needle)
558 except TypeError:
559 # e.g. 1 in 2 will raise TypeError
560 return DoesNotContain(matchee, self.needle)
561 return None
562
563
487class StartsWith(Matcher):564class StartsWith(Matcher):
488 """Checks whether one string starts with another."""565 """Checks whether one string starts with another."""
489566
@@ -613,16 +690,19 @@
613 # Catch all exceptions: Raises() should be able to match a690 # Catch all exceptions: Raises() should be able to match a
614 # KeyboardInterrupt or SystemExit.691 # KeyboardInterrupt or SystemExit.
615 except:692 except:
693 exc_info = sys.exc_info()
616 if self.exception_matcher:694 if self.exception_matcher:
617 mismatch = self.exception_matcher.match(sys.exc_info())695 mismatch = self.exception_matcher.match(exc_info)
618 if not mismatch:696 if not mismatch:
697 del exc_info
619 return698 return
620 else:699 else:
621 mismatch = None700 mismatch = None
622 # The exception did not match, or no explicit matching logic was701 # The exception did not match, or no explicit matching logic was
623 # performed. If the exception is a non-user exception (that is, not702 # performed. If the exception is a non-user exception (that is, not
624 # a subclass of Exception on Python 2.5+) then propogate it.703 # a subclass of Exception on Python 2.5+) then propogate it.
625 if isbaseexception(sys.exc_info()[1]):704 if isbaseexception(exc_info[1]):
705 del exc_info
626 raise706 raise
627 return mismatch707 return mismatch
628708
629709
=== modified file 'testtools/testcase.py'
--- testtools/testcase.py 2011-07-27 19:47:22 +0000
+++ testtools/testcase.py 2011-08-14 12:18:23 +0000
@@ -27,10 +27,14 @@
27from testtools.compat import advance_iterator27from testtools.compat import advance_iterator
28from testtools.matchers import (28from testtools.matchers import (
29 Annotate,29 Annotate,
30 Contains,
30 Equals,31 Equals,
32 MatchesAll,
31 MatchesException,33 MatchesException,
32 Is,34 Is,
35 IsInstance,
33 Not,36 Not,
37 Raises,
34 )38 )
35from testtools.monkey import patch39from testtools.monkey import patch
36from testtools.runtest import RunTest40from testtools.runtest import RunTest
@@ -304,16 +308,14 @@
304 :param observed: The observed value.308 :param observed: The observed value.
305 :param message: An optional message to include in the error.309 :param message: An optional message to include in the error.
306 """310 """
307 matcher = Annotate.if_message(message, Equals(expected))311 matcher = Equals(expected)
308 self.assertThat(observed, matcher)312 self.assertThat(observed, matcher, message)
309313
310 failUnlessEqual = assertEquals = assertEqual314 failUnlessEqual = assertEquals = assertEqual
311315
312 def assertIn(self, needle, haystack):316 def assertIn(self, needle, haystack):
313 """Assert that needle is in haystack."""317 """Assert that needle is in haystack."""
314 # XXX: Re-implement with matchers.318 self.assertThat(haystack, Contains(needle))
315 if needle not in haystack:
316 self.fail('%r not in %r' % (needle, haystack))
317319
318 def assertIsNone(self, observed, message=''):320 def assertIsNone(self, observed, message=''):
319 """Assert that 'observed' is equal to None.321 """Assert that 'observed' is equal to None.
@@ -321,8 +323,8 @@
321 :param observed: The observed value.323 :param observed: The observed value.
322 :param message: An optional message describing the error.324 :param message: An optional message describing the error.
323 """325 """
324 matcher = Annotate.if_message(message, Is(None))326 matcher = Is(None)
325 self.assertThat(observed, matcher)327 self.assertThat(observed, matcher, message)
326328
327 def assertIsNotNone(self, observed, message=''):329 def assertIsNotNone(self, observed, message=''):
328 """Assert that 'observed' is not equal to None.330 """Assert that 'observed' is not equal to None.
@@ -330,8 +332,8 @@
330 :param observed: The observed value.332 :param observed: The observed value.
331 :param message: An optional message describing the error.333 :param message: An optional message describing the error.
332 """334 """
333 matcher = Annotate.if_message(message, Not(Is(None)))335 matcher = Not(Is(None))
334 self.assertThat(observed, matcher)336 self.assertThat(observed, matcher, message)
335337
336 def assertIs(self, expected, observed, message=''):338 def assertIs(self, expected, observed, message=''):
337 """Assert that 'expected' is 'observed'.339 """Assert that 'expected' is 'observed'.
@@ -340,33 +342,25 @@
340 :param observed: The observed value.342 :param observed: The observed value.
341 :param message: An optional message describing the error.343 :param message: An optional message describing the error.
342 """344 """
343 # XXX: Re-implement with matchers.345 matcher = Is(expected)
344 if message:346 self.assertThat(observed, matcher, message)
345 message = ': ' + message
346 if expected is not observed:
347 self.fail('%r is not %r%s' % (expected, observed, message))
348347
349 def assertIsNot(self, expected, observed, message=''):348 def assertIsNot(self, expected, observed, message=''):
350 """Assert that 'expected' is not 'observed'."""349 """Assert that 'expected' is not 'observed'."""
351 # XXX: Re-implement with matchers.350 matcher = Not(Is(expected))
352 if message:351 self.assertThat(observed, matcher, message)
353 message = ': ' + message
354 if expected is observed:
355 self.fail('%r is %r%s' % (expected, observed, message))
356352
357 def assertNotIn(self, needle, haystack):353 def assertNotIn(self, needle, haystack):
358 """Assert that needle is not in haystack."""354 """Assert that needle is not in haystack."""
359 # XXX: Re-implement with matchers.355 matcher = Not(Contains(needle))
360 if needle in haystack:356 self.assertThat(haystack, matcher)
361 self.fail('%r in %r' % (needle, haystack))
362357
363 def assertIsInstance(self, obj, klass, msg=None):358 def assertIsInstance(self, obj, klass, msg=None):
364 # XXX: Re-implement with matchers.359 if isinstance(klass, tuple):
365 if msg is None:360 matcher = IsInstance(*klass)
366 msg = '%r is not an instance of %s' % (361 else:
367 obj, self._formatTypes(klass))362 matcher = IsInstance(klass)
368 if not isinstance(obj, klass):363 self.assertThat(obj, matcher, msg)
369 self.fail(msg)
370364
371 def assertRaises(self, excClass, callableObj, *args, **kwargs):365 def assertRaises(self, excClass, callableObj, *args, **kwargs):
372 """Fail unless an exception of class excClass is thrown366 """Fail unless an exception of class excClass is thrown
@@ -376,17 +370,22 @@
376 deemed to have suffered an error, exactly as for an370 deemed to have suffered an error, exactly as for an
377 unexpected exception.371 unexpected exception.
378 """372 """
379 # XXX: Re-implement with matchers.373 class ReRaiseOtherTypes(object):
380 try:374 def match(self, matchee):
381 ret = callableObj(*args, **kwargs)375 if not issubclass(matchee[0], excClass):
382 except excClass:376 raise matchee[0], matchee[1], matchee[2]
383 return sys.exc_info()[1]377 class CaptureMatchee(object):
384 else:378 def match(self, matchee):
385 excName = self._formatTypes(excClass)379 self.matchee = matchee[1]
386 self.fail("%s not raised, %r returned instead." % (excName, ret))380 capture = CaptureMatchee()
381 matcher = Raises(MatchesAll(ReRaiseOtherTypes(),
382 MatchesException(excClass), capture))
383
384 self.assertThat(lambda:callableObj(*args, **kwargs), matcher)
385 return capture.matchee
387 failUnlessRaises = assertRaises386 failUnlessRaises = assertRaises
388387
389 def assertThat(self, matchee, matcher, verbose=False):388 def assertThat(self, matchee, matcher, message='', verbose=False):
390 """Assert that matchee is matched by matcher.389 """Assert that matchee is matched by matcher.
391390
392 :param matchee: An object to match with matcher.391 :param matchee: An object to match with matcher.
@@ -395,6 +394,7 @@
395 """394 """
396 # XXX: Should this take an optional 'message' parameter? Would kind of395 # XXX: Should this take an optional 'message' parameter? Would kind of
397 # make sense. The hamcrest one does.396 # make sense. The hamcrest one does.
397 matcher = Annotate.if_message(message, matcher)
398 mismatch = matcher.match(matchee)398 mismatch = matcher.match(matchee)
399 if not mismatch:399 if not mismatch:
400 return400 return
@@ -433,6 +433,7 @@
433 be removed. This separation preserves the original intent of the test433 be removed. This separation preserves the original intent of the test
434 while it is in the expectFailure mode.434 while it is in the expectFailure mode.
435 """435 """
436 # TODO: implement with matchers.
436 self._add_reason(reason)437 self._add_reason(reason)
437 try:438 try:
438 predicate(*args, **kwargs)439 predicate(*args, **kwargs)
439440
=== modified file 'testtools/tests/test_matchers.py'
--- testtools/tests/test_matchers.py 2011-08-08 11:14:01 +0000
+++ testtools/tests/test_matchers.py 2011-08-14 12:18:23 +0000
@@ -19,6 +19,7 @@
19 AllMatch,19 AllMatch,
20 Annotate,20 Annotate,
21 AnnotatedMismatch,21 AnnotatedMismatch,
22 Contains,
22 Equals,23 Equals,
23 DocTestMatches,24 DocTestMatches,
24 DoesNotEndWith,25 DoesNotEndWith,
@@ -26,6 +27,7 @@
26 EndsWith,27 EndsWith,
27 KeysEqual,28 KeysEqual,
28 Is,29 Is,
30 IsInstance,
29 LessThan,31 LessThan,
30 GreaterThan,32 GreaterThan,
31 MatchesAny,33 MatchesAny,
@@ -179,6 +181,26 @@
179 describe_examples = [("1 is not 2", 2, Is(1))]181 describe_examples = [("1 is not 2", 2, Is(1))]
180182
181183
184class TestIsInstanceInterface(TestCase, TestMatchersInterface):
185
186 class Foo:pass
187
188 matches_matcher = IsInstance(Foo)
189 matches_matches = [Foo()]
190 matches_mismatches = [object(), 1, Foo]
191
192 str_examples = [
193 ("IsInstance(str)", IsInstance(str)),
194 ("IsInstance(str, int)", IsInstance(str, int)),
195 ]
196
197 describe_examples = [
198 ("'foo' is not an instance of int", 'foo', IsInstance(int)),
199 ("'foo' is not an instance of any of (int, type)", 'foo',
200 IsInstance(int, type)),
201 ]
202
203
182class TestLessThanInterface(TestCase, TestMatchersInterface):204class TestLessThanInterface(TestCase, TestMatchersInterface):
183205
184 matches_matcher = LessThan(4)206 matches_matcher = LessThan(4)
@@ -211,6 +233,20 @@
211 ]233 ]
212234
213235
236class TestContainsInterface(TestCase, TestMatchersInterface):
237
238 matches_matcher = Contains('foo')
239 matches_matches = ['foo', 'afoo', 'fooa']
240 matches_mismatches = ['f', 'fo', 'oo', 'faoo', 'foao']
241
242 str_examples = [
243 ("Contains(1)", Contains(1)),
244 ("Contains('foo')", Contains('foo')),
245 ]
246
247 describe_examples = [("1 not present in 2", 2, Contains(1))]
248
249
214def make_error(type, *args, **kwargs):250def make_error(type, *args, **kwargs):
215 try:251 try:
216 raise type(*args, **kwargs)252 raise type(*args, **kwargs)
217253
=== modified file 'testtools/tests/test_testcase.py'
--- testtools/tests/test_testcase.py 2011-07-26 23:48:48 +0000
+++ testtools/tests/test_testcase.py 2011-08-14 12:18:23 +0000
@@ -2,6 +2,7 @@
22
3"""Tests for extensions to the base test library."""3"""Tests for extensions to the base test library."""
44
5from doctest import ELLIPSIS
5from pprint import pformat6from pprint import pformat
6import sys7import sys
7import unittest8import unittest
@@ -20,6 +21,8 @@
20 )21 )
21from testtools.compat import _b22from testtools.compat import _b
22from testtools.matchers import (23from testtools.matchers import (
24 Annotate,
25 DocTestMatches,
23 Equals,26 Equals,
24 MatchesException,27 MatchesException,
25 Raises,28 Raises,
@@ -244,16 +247,8 @@
244 # assertRaises raises self.failureException when it's passed a247 # assertRaises raises self.failureException when it's passed a
245 # callable that raises no error.248 # callable that raises no error.
246 ret = ('orange', 42)249 ret = ('orange', 42)
247 try:250 self.assertFails("<function <lambda> at ...> returned ('orange', 42)",
248 self.assertRaises(RuntimeError, lambda: ret)251 self.assertRaises, RuntimeError, lambda: ret)
249 except self.failureException:
250 # We expected assertRaises to raise this exception.
251 e = sys.exc_info()[1]
252 self.assertEqual(
253 '%s not raised, %r returned instead.'
254 % (self._formatTypes(RuntimeError), ret), str(e))
255 else:
256 self.fail('Expected assertRaises to fail, but it did not.')
257252
258 def test_assertRaises_fails_when_different_error_raised(self):253 def test_assertRaises_fails_when_different_error_raised(self):
259 # assertRaises re-raises an exception that it didn't expect.254 # assertRaises re-raises an exception that it didn't expect.
@@ -298,15 +293,14 @@
298 failure = self.assertRaises(293 failure = self.assertRaises(
299 self.failureException,294 self.failureException,
300 self.assertRaises, expectedExceptions, lambda: None)295 self.assertRaises, expectedExceptions, lambda: None)
301 self.assertEqual(296 self.assertFails('<function <lambda> at ...> returned None',
302 '%s not raised, None returned instead.'297 self.assertRaises, expectedExceptions, lambda: None)
303 % self._formatTypes(expectedExceptions), str(failure))
304298
305 def assertFails(self, message, function, *args, **kwargs):299 def assertFails(self, message, function, *args, **kwargs):
306 """Assert that function raises a failure with the given message."""300 """Assert that function raises a failure with the given message."""
307 failure = self.assertRaises(301 failure = self.assertRaises(
308 self.failureException, function, *args, **kwargs)302 self.failureException, function, *args, **kwargs)
309 self.assertEqual(message, str(failure))303 self.assertThat(failure, DocTestMatches(message, ELLIPSIS))
310304
311 def test_assertIn_success(self):305 def test_assertIn_success(self):
312 # assertIn(needle, haystack) asserts that 'needle' is in 'haystack'.306 # assertIn(needle, haystack) asserts that 'needle' is in 'haystack'.
@@ -317,9 +311,9 @@
317 def test_assertIn_failure(self):311 def test_assertIn_failure(self):
318 # assertIn(needle, haystack) fails the test when 'needle' is not in312 # assertIn(needle, haystack) fails the test when 'needle' is not in
319 # 'haystack'.313 # 'haystack'.
320 self.assertFails('3 not in [0, 1, 2]', self.assertIn, 3, [0, 1, 2])314 self.assertFails('3 not present in [0, 1, 2]', self.assertIn, 3, [0, 1, 2])
321 self.assertFails(315 self.assertFails(
322 '%r not in %r' % ('qux', 'foo bar baz'),316 '%r not present in %r' % ('qux', 'foo bar baz'),
323 self.assertIn, 'qux', 'foo bar baz')317 self.assertIn, 'qux', 'foo bar baz')
324318
325 def test_assertNotIn_success(self):319 def test_assertNotIn_success(self):
@@ -331,9 +325,10 @@
331 def test_assertNotIn_failure(self):325 def test_assertNotIn_failure(self):
332 # assertNotIn(needle, haystack) fails the test when 'needle' is in326 # assertNotIn(needle, haystack) fails the test when 'needle' is in
333 # 'haystack'.327 # 'haystack'.
334 self.assertFails('3 in [1, 2, 3]', self.assertNotIn, 3, [1, 2, 3])328 self.assertFails('[1, 2, 3] matches Contains(3)', self.assertNotIn,
329 3, [1, 2, 3])
335 self.assertFails(330 self.assertFails(
336 '%r in %r' % ('foo', 'foo bar baz'),331 "'foo bar baz' matches Contains('foo')",
337 self.assertNotIn, 'foo', 'foo bar baz')332 self.assertNotIn, 'foo', 'foo bar baz')
338333
339 def test_assertIsInstance(self):334 def test_assertIsInstance(self):
@@ -367,7 +362,7 @@
367 """Simple class for testing assertIsInstance."""362 """Simple class for testing assertIsInstance."""
368363
369 self.assertFails(364 self.assertFails(
370 '42 is not an instance of %s' % self._formatTypes(Foo),365 "'42' is not an instance of %s" % self._formatTypes(Foo),
371 self.assertIsInstance, 42, Foo)366 self.assertIsInstance, 42, Foo)
372367
373 def test_assertIsInstance_failure_multiple_classes(self):368 def test_assertIsInstance_failure_multiple_classes(self):
@@ -381,12 +376,13 @@
381 """Another simple class for testing assertIsInstance."""376 """Another simple class for testing assertIsInstance."""
382377
383 self.assertFails(378 self.assertFails(
384 '42 is not an instance of %s' % self._formatTypes([Foo, Bar]),379 "'42' is not an instance of any of (%s)" % self._formatTypes([Foo, Bar]),
385 self.assertIsInstance, 42, (Foo, Bar))380 self.assertIsInstance, 42, (Foo, Bar))
386381
387 def test_assertIsInstance_overridden_message(self):382 def test_assertIsInstance_overridden_message(self):
388 # assertIsInstance(obj, klass, msg) permits a custom message.383 # assertIsInstance(obj, klass, msg) permits a custom message.
389 self.assertFails("foo", self.assertIsInstance, 42, str, "foo")384 self.assertFails("'42' is not an instance of str: foo",
385 self.assertIsInstance, 42, str, "foo")
390386
391 def test_assertIs(self):387 def test_assertIs(self):
392 # assertIs asserts that an object is identical to another object.388 # assertIs asserts that an object is identical to another object.
@@ -418,16 +414,17 @@
418 def test_assertIsNot_fails(self):414 def test_assertIsNot_fails(self):
419 # assertIsNot raises assertion errors if one object is identical to415 # assertIsNot raises assertion errors if one object is identical to
420 # another.416 # another.
421 self.assertFails('None is None', self.assertIsNot, None, None)417 self.assertFails('None matches Is(None)', self.assertIsNot, None, None)
422 some_list = [42]418 some_list = [42]
423 self.assertFails(419 self.assertFails(
424 '[42] is [42]', self.assertIsNot, some_list, some_list)420 '[42] matches Is([42])', self.assertIsNot, some_list, some_list)
425421
426 def test_assertIsNot_fails_with_message(self):422 def test_assertIsNot_fails_with_message(self):
427 # assertIsNot raises assertion errors if one object is identical to423 # assertIsNot raises assertion errors if one object is identical to
428 # another, and includes a user-supplied message if it's provided.424 # another, and includes a user-supplied message if it's provided.
429 self.assertFails(425 self.assertFails(
430 'None is None: foo bar', self.assertIsNot, None, None, "foo bar")426 'None matches Is(None): foo bar', self.assertIsNot, None, None,
427 "foo bar")
431428
432 def test_assertThat_matches_clean(self):429 def test_assertThat_matches_clean(self):
433 class Matcher(object):430 class Matcher(object):
@@ -468,6 +465,12 @@
468 expected = matcher.match(matchee).describe()465 expected = matcher.match(matchee).describe()
469 self.assertFails(expected, self.assertThat, matchee, matcher)466 self.assertFails(expected, self.assertThat, matchee, matcher)
470467
468 def test_assertThat_message_is_annotated(self):
469 matchee = 'foo'
470 matcher = Equals('bar')
471 expected = Annotate('woo', matcher).match(matchee).describe()
472 self.assertFails(expected, self.assertThat, matchee, matcher, 'woo')
473
471 def test_assertThat_verbose_output(self):474 def test_assertThat_verbose_output(self):
472 matchee = 'foo'475 matchee = 'foo'
473 matcher = Equals('bar')476 matcher = Equals('bar')

Subscribers

People subscribed via source and target branches