Module achoo
[hide private]
[frames] | no frames]

Source Code for Module achoo

  1  # 
  2  # Achoo. A fluent interface for testing Python objects. 
  3  # Copyright (C) 2008 Quuxo Software. 
  4  # <http://web.quuxo.com/projects/achoo> 
  5  # 
  6  # This program is free software: you can redistribute it and/or modify 
  7  # it under the terms of the GNU Lesser General Public License as 
  8  # published by the Free Software Foundation, either version 3 of the 
  9  # License, or (at your option) any later version. 
 10  # 
 11  # This program is distributed in the hope that it will be useful, 
 12  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 14  # GNU General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU Lesser General Public 
 17  # License along with this program.  If not, see 
 18  # <http://www.gnu.org/licenses/>. 
 19  # 
 20   
 21  """ 
 22  Achoo is a fluent interface for testing Python objects. 
 23   
 24  It is designed to be used in conjunction with a unit testing 
 25  framework like PyUnit's C{unittest} module, shipped with all modern 
 26  Python distributions. 
 27   
 28  To use Achoo, import the assertion builder functions then use them 
 29  to wire up assertions about objects and callables. 
 30   
 31  The two assertion builder functions are C{requiring} - used to test 
 32  properties of objects and C{calling}, used to test properties of a 
 33  calling a callable object (that is, a function, method or similar). 
 34  These functions returns assertion builders that can be used to chain 
 35  assertions calls together. See the documentation for the functions 
 36  for more information. 
 37   
 38  If any of the assertions are not met, an C{AssertionError} is raised. 
 39   
 40  For example:: 
 41   
 42      import unittest 
 43      from achoo import requiring 
 44      from achoo import calling 
 45   
 46      class StringTest(unittest.TestCase): 
 47   
 48          def testLength(self): 
 49              s = 'foo' 
 50              requiring(s.length).equal_to(3) 
 51   
 52          def testStrip(self): 
 53              s = ' foo ' 
 54              calling(s.strip).returns('foo') 
 55   
 56          def testSplit(self): 
 57              s = 'foo,bar' 
 58              calling(s.split).passing(',').returns()\  
 59                  .length(2)\  
 60                  .index(0).equal_to('foo')\  
 61                  .index(1).equal_to('bar') 
 62   
 63          def testBadIndex(self): 
 64              s = 'foo' 
 65              calling(s.index).passing('quux').raises(ValueError) 
 66   
 67  """ 
 68   
 69  import sys 
 70   
 71  import gettext 
 72  _ = gettext.translation('achoo', fallback=True).ugettext 
 73   
 74   
75 -def requiring(value):
76 """ 77 Assertion builder factory for object properties. 78 79 To test an object, call C{requiring} and pass the object as the 80 sole argument. A C{ValueAssertionBuilder} is returned and can be used 81 to chain together assertions about it. 82 83 For example:: 84 85 test_map = {'foo': 'bar'} 86 requiring(test_map)\ 87 .length(1)\ 88 .contains('foo')\ 89 .index('foo').equal_to('bar') 90 91 @return: an instance of C{ValueAssertionBuilder} wrapping the value 92 passed in 93 @param value: an object to be tested 94 """ 95 # XXX maybe put some logging here? what about lookup 96 # for different builder types depending on the given type? 97 # otherwise this could probably be an alias for the builder 98 # constructor 99 return ValueAssertionBuilder(value)
100 101
102 -class ValueAssertionBuilder(object):
103 """ 104 An assertion builder for testing properties of objects. 105 106 This object can be used to create a set of assertions about various 107 properties of an object. Most methods return a builder with the 108 same object so that more than one assertion to be made about it. 109 110 If any of the assertions fail, an C{AssertionError} is raised. 111 """ 112
113 - def __init__(self, value, invert=False):
114 """ 115 Constructs a new builder. 116 117 In general, you want to use the C{requiring} function instead 118 of this directly. 119 120 @param value: an object to be tested 121 @param invert: optionally inverts the sense of the next assertion 122 if C{True} 123 """ 124 self.value = value 125 self.invert_sense = invert
126 127 128 @property
129 - def is_not(self):
130 """ 131 Inverts the sense of the next assertion. 132 133 This property causes the boolean sense of the next assertion 134 to be inverted. That is, if a call to C{equal_to} is prefixed 135 with C{is_not}, it will raise an error if the value object is 136 not equal to the given value. All other subsequent assertions 137 retain the specified sense unless also prefixed with C{is_not}. 138 139 For example:: 140 141 s = 'foo' 142 requiring(s.length).is_not.equal_to(0) 143 144 """ 145 return ValueAssertionBuilder(self.value, True)
146 147
148 - def equal_to(self, other):
149 """ 150 Asserts the value object is equal to some other object. 151 152 @return: this assertion builder 153 @param other: another object to test against the builder's 154 value object 155 @raise AssertionError: if the builder's value is not equal to 156 C{other} 157 """ 158 if self.value != other and not self.invert_sense: 159 raise self._error(_('Value `%s\' expected to equal `%s\''), 160 _('Value `%s\' not expected to equal `%s\''), 161 other) 162 self.invert_sense = False 163 return self
164 165
166 - def same_as(self, other):
167 """ 168 Asserts the value object is the same as another object. 169 170 @return: this assertion builder 171 @param other: another object to test for same identity 172 @raise AssertionError: if the builder's value is not the same 173 object as C{other} 174 """ 175 if self.value is not other and not self.invert_sense: 176 raise self._error(_('Value `%s\' expected to be `%s\''), 177 _('Value `%s\' not expected to be `%s\''), 178 other) 179 self.invert_sense = False 180 return self
181 182
183 - def is_none(self):
184 """ 185 Asserts the value object is C{None}. 186 187 @return: this assertion builder 188 @raise AssertionError: if the builder's value is not C{None} 189 """ 190 return self.same_as(None)
191 192
193 - def is_not_none(self):
194 """ 195 Asserts the value object is not C{None}. 196 197 @return: this assertion builder 198 @raise AssertionError: if the builder's value is C{None} 199 """ 200 if self.value is None and not self.invert_sense: 201 raise self._error(_('Value `%s\' expected to be `%s\''), 202 _('Value `%s\' not expected to be `%s\''), 203 None) 204 self.invert_sense = False 205 return self
206 207
208 - def is_a(self, clazz):
209 """ 210 Asserts the value object is an instance of a particular type. 211 212 @return: this assertion builder 213 @param clazz: type the value must be an instance of 214 @raise AssertionError: if the builder's value is not an instance 215 of C{clazz} 216 """ 217 if not isinstance(self.value, clazz) and not self.invert_sense: 218 raise self._error(_('Value `%s\' expected to be a `%s\''), 219 _('Value `%s\' not expected to be a `%s\''), 220 clazz) 221 self.invert_sense = False 222 return self
223 224
225 - def length(self, length):
226 """ 227 Asserts the value object has a specific length. 228 229 @return: this assertion builder 230 @param length: the value that must be returned by passing 231 the builder value to the C{len} built-in 232 @raise AssertionError: if the length of the builder's value is 233 not equal to C{length} 234 """ 235 if len(self.value) != length and not self.invert_sense: 236 raise self._error(_('Length of `%s\' expected to equal `%s\''), 237 _('Length of `%s\' not expected to equal `%s\''), 238 length) 239 self.invert_sense = False 240 return self
241 242
243 - def contains(self, element):
244 """ 245 Asserts the value object contains a specific element. 246 247 @return: this assertion builder 248 @param element: the element that must be contained by the 249 value object, as tested using the keyword C{in} 250 @raise AssertionError: if the builder's value does not contain 251 C{element} 252 """ 253 if element not in self.value and not self.invert_sense: 254 raise self._error(_('Value `%s\' expected to contain `%s\''), 255 _('Value of `%s\' not expected to contain `%s\''), 256 element) 257 self.invert_sense = False 258 return self
259 260
261 - def index(self, index):
262 """ 263 Asserts the value object has a specific index. 264 265 B{Note:} this method returns a builder for the object at the 266 given index, allowing assertions to be made about that object 267 but not allowing any additional assertions to be made about 268 the original object. 269 270 The C{is_not} modifier has no effect on this method. 271 272 For example:: 273 274 test_map = {'foo': 'bar'} 275 requiring(test_map).index('foo').equal_to('bar') 276 277 @return: an assertion builder for the object at the given 278 index 279 @param index: the index that must be contained by the 280 value object, as tested using the keyword C{in} 281 @raise AssertionError: if the builder's value does not contain 282 an element at C{index} 283 """ 284 if self.invert_sense: 285 raise AssertionError\ 286 (_('A call to `index\' cannot be preceded by `is_not\'')) 287 288 try: 289 return ValueAssertionBuilder(self.value[index]) 290 except KeyError: 291 raise self._error(_('Value `%s\' expected to contain key `%s\''), 292 None, index) 293 except IndexError: 294 raise self._error(_('Value `%s\' expected to contain index `%s\''), 295 None, index)
296
297 - def _error(self, message, inverse_message, other):
298 """ 299 Returns a new C{AssertionError} with an appropriate message. 300 """ 301 return AssertionError((message 302 if not self.invert_sense 303 else inverse_message) % (self.value, other))
304 305
306 -def calling(callabl):
307 """ 308 Assertion builder factory for callable objects. 309 310 To test a callable, call C{requiring} and pass the object as the 311 sole argument. A C{ValueAssertionBuilder} is returned and can be used 312 to chain together assertions about it. 313 314 For example:: 315 316 incr = lambda x: x + 1 317 calling(incr).passing(1).returns(2) 318 calling(incr).raises(TypeError) 319 320 @return: an instance of C{CallableAssertionBuilder} wrapping the 321 callable passed in 322 @param callabl: a callable object (function, method or similar) to 323 be tested 324 """ 325 # XXX maybe put some logging here? what about lookup 326 # for different builder types depending on the given type? 327 # otherwise this could probably be an alias for the builder 328 # constructor 329 return CallableAssertionBuilder(callabl)
330 331
332 -class CallableAssertionBuilder(object):
333 """ 334 An assertion builder for testing callable objects. 335 336 This object can be used to create a set of assertions about 337 conditions when calling a callable object, such as a function 338 or method. 339 340 To provide parameters to the callable, use the C{passing} method. 341 The callable is not actually executed until one of the return 342 or raises methods is called. 343 """ 344
345 - def __init__(self, callabl):
346 """ 347 Constructs a new builder. 348 349 In general, you want to use the C{calling} function instead 350 of this directly. 351 352 @param callabl: an object to be tested 353 """ 354 self.callable = callabl 355 self.args = None 356 self.kwargs = None
357
358 - def passing(self, *args, **kwargs):
359 """ 360 Applies a set of arguments to be passed to the callable. 361 362 Use this method to specify what positional and keyword arguments 363 should be passed to the callable. 364 365 @return: this assertion builder 366 @param args: positional arguments to be passed to the callable 367 @param kwargs: keyword arguments to be passed to the callable 368 """ 369 self.args = args 370 self.kwargs = kwargs 371 return self
372
373 - def returns(self, value=None):
374 """ 375 Invokes the callable, optionally checking the returned value. 376 377 Calling this method will cause the callable to be invoked, 378 with any arguments specified using C{passing} and returning 379 a C{ValueAssertionBuilder} for the object returned by the 380 callable. 381 382 An object can be optionally passed to this method for 383 conveniently checking the value of the object returned 384 by the callable. 385 386 @return: a C{ValueAssertionBuilder} for the object returned 387 by the invocation of the callable 388 @param value: optional value that must be equal to the 389 object returned by invoking the callable 390 @raise AssertionError: if the returned value is not equal to 391 C{value} 392 """ 393 ret = self._invoke() 394 builder = requiring(ret) 395 if value is not None: 396 builder.equal_to(value) 397 return builder
398
399 - def returns_none(self):
400 """ 401 Invokes the callable and ensures the return value is C{None}. 402 403 Calling this method will cause the callable to be invoked, 404 with any arguments specified using C{passing}. 405 406 @raise AssertionError: if the value returned by invoking 407 the callable is not equal to C{None} 408 """ 409 self.returns().is_none()
410
411 - def raises(self, error):
412 """ 413 Invokes the callable, ensuring it raises an exception. 414 415 Calling this method will cause the callable to be invoked, 416 with any arguments specified using C{passing}. 417 418 A C{ValueAssertionBuilder} for the exception is returned, 419 allowing its properties to be examined. 420 421 @return: a C{ValueAssertionBuilder} for the exception raised 422 by the invocation of the callable 423 @param error: type of the exception to be raised 424 @raise AssertionError: if the callable invocation did not 425 raise an exception or if it raised an exception that was 426 not of type C{BaseException} 427 """ 428 try: 429 self._invoke() 430 except: 431 e_type, e_value, tb = sys.exc_info() 432 if e_type == error: 433 return requiring(e_value) 434 435 raise AssertionError(_('Calling `%s\' raised a `%s\' error') 436 % (self.callable, e_type)) 437 else: 438 raise AssertionError(_('Calling `%s\' did not raise any error') 439 % self.callable)
440
441 - def _invoke(self):
442 """ 443 Invokes the callable with any parameters that have been specified. 444 445 @return: the return value from the callable invocation 446 """ 447 if self.args and self.kwargs: 448 return self.callable(*self.args, **self.kwargs) 449 if self.args: 450 return self.callable(*self.args) 451 if self.kwargs: 452 return self.callable(**self.kwargs) 453 return self.callable()
454