Permalink
Browse files

bdd support

  • Loading branch information...
1 parent ea50d3e commit dc78716d0e56a00d50ff08426f7405df2edec47e @gabrielfalcao committed Feb 24, 2012
Showing with 203 additions and 51 deletions.
  1. +1 −1 README.md
  2. +88 −22 sure/__init__.py
  3. +114 −28 test_sure.py
View
@@ -1,5 +1,5 @@
# sure
-> Version 0.7.0
+> Version 0.8.0
# What
View
@@ -33,7 +33,23 @@
from pprint import pformat
from copy import deepcopy
from collections import Iterable
-version = '0.7.0'
+version = '0.8.0'
+
+
+def _get_file_name(func):
+ try:
+ name = inspect.getfile(func)
+ except AttributeError:
+ name = func.func_code.co_filename
+
+ return os.path.abspath(name)
+
+
+def _get_line_number(func):
+ try:
+ return inspect.getlineno(func)
+ except AttributeError:
+ return func.func_code.co_firstlineno
def itemize_length(items):
@@ -50,11 +66,13 @@ class VariablesBag(dict):
__varnames__ = None
__sure_actions_ran__ = None
__sure_action_results__ = None
+ __sure_providers_of__ = None
def __init__(self, *args, **kw):
self.__varnames__ = []
self.__sure_actions_ran__ = []
self.__sure_action_results__ = []
+ self.__sure_providers_of__ = {}
return super(VariablesBag, self).__init__(*args, **kw)
def __setattr__(self, attr, value):
@@ -230,17 +248,17 @@ def raises(self, exc, msg=None):
self._src, exc, e.__class__))
if isinstance(msg, basestring) and msg not in unicode(e):
- raise AssertionError('%r raised %s, but the exception message does not match. Expected %r, got %r' % (self._src, e, msg, unicode(e)))
+ raise AssertionError('%r raised %s, but the exception message does not match.\n\nEXPECTED:\n%r\n\nGOT:\n%r' % (self._src, type(e).__name__, msg, str(e)))
elif isinstance(msg, basestring) and msg not in unicode(e):
- raise AssertionError('When calling %r the exception message does not match. Expected %r, got %r' % (self._src, msg, unicode(e)))
+ raise AssertionError('When calling %r the exception message does not match. Expected: %r\n got:\n %r' % (self._src, msg, str(e)))
else:
raise e
else:
- _src_filename = inspect.getfile(self._src)
+ _src_filename = _get_file_name(self._src)
if inspect.isfunction(self._src):
- _src_lineno = inspect.getlineno(self._src)
+ _src_lineno = _get_line_number(self._src)
raise AssertionError(
'calling function %s(%s at line: "%d") with args %r and kwargs %r did not raise %r' % (
self._src.__name__,
@@ -587,29 +605,77 @@ def word_to_number(word):
'sure supports only literal numbers from one to twelve, ' \
'you tried the word "twenty"')
+def action_for(context, provides=None, depends_on=None):
+ if not provides:
+ provides = []
+
+ if not depends_on:
+ depends_on = []
+
+ def register_providers(func, attr):
+ if not attr in context.__sure_providers_of__:
+ context.__sure_providers_of__[attr] = []
+
+ context.__sure_providers_of__[attr].append(func)
+
+ def ensure_providers(func, attr):
+ assert hasattr(context, attr), \
+ 'the action "%s" was supposed to provide the attribute "%s" ' \
+ 'into the context, but it did not. Please double check its ' \
+ 'implementation' % (func.__name__, attr)
+
+ dependency_error_lonely = 'the action "%s" defined at %s:%d ' \
+ 'depends on the attribute "%s" to be available in the' \
+ ' context. It turns out that there are no actions providing ' \
+ 'that. Please double-check the implementation'
+
+ dependency_error_hints = 'the action "%s" defined at %s:%d ' \
+ 'depends on the attribute "%s" to be available in the context.'\
+ ' You need to call one of the following actions beforehand:\n'
+
+ def check_dependencies(func):
+ action = func.__name__
+ filename = _get_file_name(func)
+ lineno = _get_line_number(func)
+
+ for dependency in depends_on:
+ if dependency in context.__sure_providers_of__:
+ providers = context.__sure_providers_of__[dependency]
+ err = dependency_error_hints % (
+ action,
+ filename,
+ lineno,
+ dependency,
+ )
+ err += '\n'.join([
+ ' -> %s at %s:%d' % (
+ p.__name__,
+ _get_file_name(p),
+ _get_line_number(p)) for p in providers])
+
+ else:
+ err = dependency_error_lonely % (
+ action,
+ filename,
+ lineno,
+ dependency,
+ )
+
+ assert dependency in context, err
-def action_in(scenario):
def decorate_and_absorb(func):
+ [register_providers(func, attr) for attr in provides]
+
@wraps(func)
def wrapper(*args, **kw):
- scenario.__sure_actions_ran__.append((func, args, kw))
+ context.__sure_actions_ran__.append((func, args, kw))
+ check_dependencies(func)
result = func(*args, **kw)
- scenario.__sure_action_results__.append(result)
-
- return scenario
+ [ensure_providers(func, attr) for attr in provides]
+ context.__sure_action_results__.append(result)
+ return context
- setattr(scenario, func.__name__, wrapper)
+ setattr(context, func.__name__, wrapper)
return wrapper
- if not hasattr(scenario, 'contextualized_as'):
-
- def contextualized_as(name):
- last_result = scenario.__sure_action_results__[-1]
- setattr(scenario, name, last_result)
- return scenario
-
- scenario.contextualized_as = contextualized_as
-
return decorate_and_absorb
-
-action_for = action_in
View
@@ -811,47 +811,133 @@ def robert_is_within_context(context):
)
-def test_action_can_be_contextualized():
- "stuff returned by functions under sure.action_in can be contextualized"
- from sure import action_in, that, scenario
+def test_actions_returns_context():
+ "the actions always returns the context"
+ from sure import action_for, that, scenario
def with_setup(context):
- @action_in(context)
- def i_have_an_action(received_text):
- assert_equals(received_text, "yay, I do!")
- return "this pretty text"
+ @action_for(context)
+ def action1():
+ pass
- @scenario([with_setup])
- def i_can_use_actions(context):
- given = the = context
- given.i_have_an_action("yay, I do!").contextualized_as('value')
+ @action_for(context)
+ def action2():
+ pass
- assert that(the.value).equals("this pretty text")
+ @scenario(with_setup)
+ def i_can_use_actions(context):
+ assert that(context.action1()).equals(context)
+ assert that(context.action2()).equals(context)
return True
assert i_can_use_actions()
-def test_action_can_be_contextualized_aliased():
- "sure.action_for is an alias for sure.action_in"
- from sure import action_for, that, scenario
+def test_actions_providing_variables_in_the_context():
+ "the actions should be able to declare the variables they provide"
+ from sure import action_for, scenario
def with_setup(context):
- @action_for(context)
- def i_have_an_action(received_text):
- assert_equals(received_text, "super cool")
- return "this other pretty text"
+ @action_for(context, provides=['var1', 'foobar'])
+ def the_context_has_variables():
+ context.var1 = 123
+ context.foobar = "qwerty"
+
+ @scenario(with_setup)
+ def the_providers_are_working(Then):
+ Then.the_context_has_variables()
+ assert hasattr(Then, 'var1')
+ assert hasattr(Then, 'foobar')
+ assert hasattr(Then, '__sure_providers_of__')
+
+ providers = Then.__sure_providers_of__
+ action = Then.the_context_has_variables.__name__
+
+ providers_of_var1 = [p.__name__ for p in providers['var1']]
+ assert that(providers_of_var1).contains(action)
- @scenario([with_setup])
- def scenario_above(context):
- given = the = context
- given.i_have_an_action("super cool").contextualized_as('awesomeness')
+ providers_of_foobar = [p.__name__ for p in providers['foobar']]
+ assert that(providers_of_foobar).contains(action)
- assert that(the.awesomeness).equals("this other pretty text")
- the.awesomeness = 'was amazing'
- return the['awesomeness']
+ return True
+
+ assert the_providers_are_working()
+
+
+def test_fails_when_action_doesnt_fulfill_the_agreement_of_provides():
+ "it fails when an action doesn't fulfill its agreements"
+ from sure import action_for, scenario
+
+ error = 'the action "bad_action" was supposed to provide the ' \
+ 'attribute "two" into the context, but it did not. Please ' \
+ 'double check its implementation'
+
+ def with_setup(context):
+ @action_for(context, provides=['one', 'two'])
+ def bad_action():
+ context.one = 123
+
+ @scenario(with_setup)
+ def the_providers_are_working(the):
+ assert that(the.bad_action).raises(AssertionError, error)
+ return True
+
+ assert the_providers_are_working()
+
+
+def test_depends_on_failing_due_nothing_found():
+ "it fails when an action depends on some attribute that is not " \
+ "provided by any other previous action"
+ import os
+ from sure import action_for, scenario
+
+ fullpath = os.path.abspath(__file__)
+ error = 'the action "lonely_action" defined at %s:901 ' \
+ 'depends on the attribute "something" to be available in the' \
+ ' context. It turns out that there are no actions providing ' \
+ 'that. Please double-check the implementation' % fullpath
+
+ def with_setup(context):
+ @action_for(context, depends_on=['something'])
+ def lonely_action():
+ pass
+
+ @scenario(with_setup)
+ def depends_on_fails(the):
+ assert that(the.lonely_action).raises(AssertionError, error)
+ return True
+
+ assert depends_on_fails()
+
+
+def test_depends_on_failing_due_not_calling_a_previous_action():
+ "it fails when an action depends on some attribute that is being " \
+ "provided by other actions"
+
+ import os
+ from sure import action_for, scenario
+
+ fullpath = os.path.abspath(__file__)
+ error = 'the action "my_action" defined at {0}:931 ' \
+ 'depends on the attribute "some_attr" to be available in the context.'\
+ ' You need to call one of the following actions beforehand:\n' \
+ ' -> dependency_action at {0}:927'.format(fullpath)
+
+ def with_setup(context):
+ @action_for(context, provides=['some_attr'])
+ def dependency_action():
+ context.some_attr = True
+
+ @action_for(context, depends_on=['some_attr'])
+ def my_action():
+ pass
+
+ @scenario(with_setup)
+ def depends_on_fails(the):
+ assert that(the.my_action).raises(AssertionError, error)
+ return True
- assert_equals(scenario_above(), 'was amazing')
+ assert depends_on_fails()
def test_that_contains_dictionary_keys():
@@ -903,7 +989,7 @@ def access_nonexisting_attr():
assert that(access_nonexisting_attr).raises(
AssertionError,
- 'you have tried to access the attribute "bleh" the context ' \
+ 'you have tried to access the attribute "bleh" from the context ' \
'(aka VariablesBag), but there is no such attribute assigned to it. ' \
'Maybe you misspelled it ? Well, here are the options: ' \
'[\'name\', \'foo\']',

0 comments on commit dc78716

Please sign in to comment.