Merge lp:~abentley/testtools/expected-exception into lp:~testtools-committers/testtools/trunk

Proposed by Aaron Bentley
Status: Merged
Merged at revision: 170
Proposed branch: lp:~abentley/testtools/expected-exception
Merge into: lp:~testtools-committers/testtools/trunk
Diff against target: 110 lines (+69/-0)
4 files modified
testtools/__init__.py (+1/-0)
testtools/testcase.py (+22/-0)
testtools/tests/test_testtools.py (+6/-0)
testtools/tests/test_with_with.py (+40/-0)
To merge this branch: bzr merge lp:~abentley/testtools/expected-exception
Reviewer Review Type Date Requested Status
Jonathan Lange Pending
Review via email: mp+46858@code.launchpad.net

Description of the change

This introduces the ExpectedException context manager for nicer error handling.
The tests are split into a separate file because they use the with statement.
The exec line is used because future imports cannot be conditionally imported.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

This seems to reimplement the exception matching logic of
matches_exception etc from testtools.matchers. Did you consider
reusing that?

Revision history for this message
Aaron Bentley (abentley) wrote :

No. When I discussed how and where to land it with Jono, he didn't mention anything like that.

Revision history for this message
Jonathan Lange (jml) wrote :

Thanks Aaron.

I've taken your branch and:
 * Update copyright years in:
   * testcase.py
   * LICENSE
   * __init__.py
 * Update copyright years in LICENSE
 * Added to NEWS
 * Added Aaron to contributor list in README
 * Beefed up docstring
 * Re-exported from testcase/__init__.py (added to __all__)
 * Added to __all__ in testcase.py
 * Updated MatchesException to also take an optional regex
 * Updated for-test-authors.rst to describe ExpectedException

I also tried out Rob's suggestion of using MatchesException. However, the re-raise logic of the context manager, plus the way that context managers can get None for exc_type means that there is very little code re-use, so I've left things pretty much the way you wrote them.

See lp:~jml/testtools/expected-exception, or trunk rsn.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'testtools/__init__.py'
2--- testtools/__init__.py 2010-12-18 07:21:34 +0000
3+++ testtools/__init__.py 2011-01-20 00:09:53 +0000
4@@ -37,6 +37,7 @@
5 )
6 from testtools.testcase import (
7 ErrorHolder,
8+ ExpectedException,
9 PlaceHolder,
10 TestCase,
11 clone_test_with_new_id,
12
13=== modified file 'testtools/testcase.py'
14--- testtools/testcase.py 2011-01-13 22:32:02 +0000
15+++ testtools/testcase.py 2011-01-20 00:09:53 +0000
16@@ -14,6 +14,7 @@
17
18 import copy
19 import itertools
20+import re
21 import sys
22 import types
23 import unittest
24@@ -682,3 +683,24 @@
25 def _id(obj):
26 return obj
27 return _id
28+
29+
30+class ExpectedException:
31+ """A context manager to handle expected exceptions."""
32+
33+ def __init__(self, exc_type, value_re):
34+ self.exc_type = exc_type
35+ self.value_re = value_re
36+
37+ def __enter__(self):
38+ pass
39+
40+ def __exit__(self, exc_type, exc_value, traceback):
41+ if exc_type is None:
42+ raise AssertionError('%s not raised.' % self.exc_type.__name__)
43+ if exc_type != self.exc_type:
44+ return False
45+ if not re.match(self.value_re, str(exc_value)):
46+ raise AssertionError('"%s" does not match "%s".' %
47+ (str(exc_value), self.value_re))
48+ return True
49
50=== modified file 'testtools/tests/test_testtools.py'
51--- testtools/tests/test_testtools.py 2010-12-13 01:15:11 +0000
52+++ testtools/tests/test_testtools.py 2011-01-20 00:09:53 +0000
53@@ -30,6 +30,12 @@
54 Python27TestResult,
55 ExtendedTestResult,
56 )
57+try:
58+ exec('from __future__ import with_statement')
59+except SyntaxError:
60+ pass
61+else:
62+ from test_with_with import *
63
64
65 class TestPlaceHolder(TestCase):
66
67=== added file 'testtools/tests/test_with_with.py'
68--- testtools/tests/test_with_with.py 1970-01-01 00:00:00 +0000
69+++ testtools/tests/test_with_with.py 2011-01-20 00:09:53 +0000
70@@ -0,0 +1,40 @@
71+from __future__ import with_statement
72+
73+from testtools import (
74+ ExpectedException,
75+ TestCase,
76+ )
77+
78+class TestExpectedException(TestCase):
79+ """Test the ExpectedException context manager."""
80+
81+ def test_pass_on_raise(self):
82+ with ExpectedException(ValueError, 'tes.'):
83+ raise ValueError('test')
84+
85+ def test_raise_on_text_mismatch(self):
86+ try:
87+ with ExpectedException(ValueError, 'tes.'):
88+ raise ValueError('mismatch')
89+ except AssertionError, e:
90+ self.assertEqual('"mismatch" does not match "tes.".', str(e))
91+ else:
92+ self.fail('AssertionError not raised.')
93+
94+ def test_raise_on_error_mismatch(self):
95+ try:
96+ with ExpectedException(TypeError, 'tes.'):
97+ raise ValueError('mismatch')
98+ except ValueError, e:
99+ self.assertEqual('mismatch', str(e))
100+ else:
101+ self.fail('ValueError not raised.')
102+
103+ def test_raise_if_no_exception(self):
104+ try:
105+ with ExpectedException(TypeError, 'tes.'):
106+ pass
107+ except AssertionError, e:
108+ self.assertEqual('TypeError not raised.', str(e))
109+ else:
110+ self.fail('AssertionError not raised.')

Subscribers

People subscribed via source and target branches