Permalink
Browse files

just some brainstorming

  • Loading branch information...
1 parent 87d8afb commit 4b5db1bb7ee8d4c674987042b084b0ed295e6b5b @gabrielfalcao committed Jun 13, 2012
Showing with 161 additions and 6 deletions.
  1. +7 −0 TODO.md
  2. +1 −1 lettuce/__init__.py
  3. +15 −2 lettuce/core.py
  4. +40 −2 lettuce/decorators.py
  5. +5 −0 lettuce/exceptions.py
  6. +2 −1 lettuce/registry.py
  7. +91 −0 tests/unit/test_step_transformations.py
View
@@ -0,0 +1,7 @@
+before 0.2.0:
+ * https://github.com/gabrielfalcao/lettuce/pull/224
+ * https://github.com/gabrielfalcao/lettuce/pull/255
+ * https://github.com/gabrielfalcao/lettuce/pull/256
+ * all the "high-priority" labeled tickets
+ * https://github.com/gabrielfalcao/lettuce/issues/103 (maybe)
+ * https://github.com/gabrielfalcao/lettuce/issues/198
View
@@ -31,7 +31,7 @@
from lettuce.decorators import step
from lettuce.registry import call_hook
-from lettuce.registry import STEP_REGISTRY
+from lettuce.registry import STEP_REGISTRY, CASTER_REGISTRY
from lettuce.registry import CALLBACK_REGISTRY
from lettuce.exceptions import StepLoadingError
from lettuce.plugins import xunit_output
View
@@ -24,7 +24,7 @@
from lettuce import strings
from lettuce import languages
from lettuce.fs import FileSystem
-from lettuce.registry import STEP_REGISTRY
+from lettuce.registry import STEP_REGISTRY, CASTER_REGISTRY
from lettuce.registry import call_hook
from lettuce.exceptions import ReasonToFail
from lettuce.exceptions import NoDefinitionFound
@@ -110,16 +110,29 @@ class StepDefinition(object):
"""A step definition is a wrapper for user-defined callbacks. It
gets a few metadata from file, such as filename and line number"""
def __init__(self, step, function):
+ self.step = step
self.function = function
self.file = fs.relpath(function.func_code.co_filename)
self.line = function.func_code.co_firstlineno + 1
- self.step = step
+
+ def _apply_casters(self, step, args):
+ sentence = step.sentence
+ if not args:
+ return ()
+
+ for regex, caster in CASTER_REGISTRY.items():
+ matched = re.search(regex, sentence)
+ if matched:
+ args = (caster(*args), )
+
+ return args
def __call__(self, *args, **kw):
"""Method that actually wrapps the call to step definition
callback. Sends step object as first argument
"""
try:
+ args = self._apply_casters(self.step, args)
ret = self.function(self.step, *args, **kw)
self.step.passed = True
except Exception, e:
View
@@ -15,8 +15,43 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
-from lettuce.core import STEP_REGISTRY
-from lettuce.exceptions import StepLoadingError
+from lettuce.core import STEP_REGISTRY, CASTER_REGISTRY
+from lettuce.exceptions import CasterLoadingError, StepLoadingError
+
+
+def caster(regex):
+ """Decorates a function that will be responsible for casting
+ matched regex into object, leveraging more flexibility on step
+ definitions and simplifying the test ecosystem.
+
+ Example::
+
+ >>> from lettuce import step
+ >>> from models import Contact
+ >>>
+ >>> @step.caster(r'the person "(.*)"')
+ ... def into_a_contact_object(value):
+ ... return Contact.objects.get(name=value)
+ ...
+ >>> @step(r'Given the person "(.*)" is erased from my address book')
+ ... def given_i_do_something(step, user):
+ ... user.delete()
+
+ Notice the name matched in the regex below being cast into a
+ `Contact` object
+ """
+ def wrap(func):
+ try:
+ re.compile(regex)
+ except re.error, e:
+ raise CasterLoadingError("Error when trying to compile:\n"
+ " regex: %r\n"
+ " for function: %s\n"
+ " error: %s" % (regex, func, e))
+ CASTER_REGISTRY[regex] = func
+ return func
+
+ return wrap
def step(regex):
@@ -36,6 +71,7 @@ def step(regex):
Notice that all step definitions take a step object as argument.
"""
+
def wrap(func):
try:
re.compile(regex)
@@ -48,3 +84,5 @@ def wrap(func):
return func
return wrap
+
+step.caster = caster
View
@@ -50,3 +50,8 @@ def __init__(self, filename, string):
class StepLoadingError(Exception):
"""Raised when a step cannot be loaded."""
pass
+
+
+class CasterLoadingError(Exception):
+ """Raised when a caster's regex cannot be compiled."""
+ pass
View
@@ -38,8 +38,8 @@ def clear(self):
for callback_list in action_dict.values():
callback_list[:] = []
-
STEP_REGISTRY = {}
+CASTER_REGISTRY = {}
CALLBACK_REGISTRY = CallbackDict(
{
'all': {
@@ -91,4 +91,5 @@ def call_hook(situation, kind, *args, **kw):
def clear():
STEP_REGISTRY.clear()
+ CASTER_REGISTRY.clear()
CALLBACK_REGISTRY.clear()
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+# <Lettuce - Behaviour Driven Development for python>
+# Copyright (C) <2010-2012> Gabriel Falcão <gabriel@nacaolivre.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import sys
+from lettuce import step, world, Feature
+from sure import that, scenario
+
+FEATURE1 = '''
+Feature: Transformations
+ Scenario: Simple matching
+ Given the following users:
+ | name | email |
+ | Gabriel | gabriel@lettuce.it |
+ | Lincoln | lincoln@comum.org |
+ When the user "Gabriel" is mentioned
+ Then it becomes available in `world` as "last_user"
+ And it is an `User` instance
+'''
+
+THIS_MODULE = sys.modules[__name__]
+
+
+def step_runner_environ(context):
+ "Make sure the test environment is what is expected"
+
+ from lettuce import registry
+ registry.clear()
+
+ world.users = {}
+
+ class User:
+ def __init__(self, name, email):
+ self.name = name
+ self.email = email
+
+ def save(self):
+ world.users[self.name] = self
+
+ @step('Given the following users')
+ def collect_users(step):
+ for data in step.hashes:
+ user = User(**data)
+ user.save()
+
+ @step(r'When the user "(\w+)" is mentioned')
+ def mention_user(step, user):
+ world.last_user = user
+
+ @step(r'Then it becomes available.* as "([\w_]+)"')
+ def becomes_available(step, attribute):
+ assert (hasattr(world, attribute),
+ 'world should contain the attribute %s' % attribute)
+
+ @step(r'And it is an `(\w+)` instance')
+ def and_is_instance_of(step, klass):
+ import ipdb;ipdb.set_trace()
+ assert that(world.last_user).is_a(klass)
+
+
+@scenario(step_runner_environ)
+def test_transformations(context):
+
+ @step.caster(r'the user "(\w+)" is mentioned')
+ def capture_users_by_name(name):
+ return world.users[name]
+
+ @step.caster(r'it is an `(\w+)`')
+ def get_class_by_name(name):
+ return THIS_MODULE[name]
+
+ f = Feature.from_string(FEATURE1)
+ feature_result = f.run()
+ scenario_result = feature_result.scenario_results[0]
+
+ assert that(scenario_result.steps_undefined).equals([])
+ assert that(scenario_result.steps_failed).equals([])
+
+ assert feature_result.passed

0 comments on commit 4b5db1b

Please sign in to comment.