Use Multi-Methods in Step Definitions¶
Assume you have a number of rather similar steps, like:
# file:step_matcher.features/use_multi_methods.feature
Feature: Use Multi-Methods in Step Definitions
Scenario:
Given I go to a shop
When I buy 2 cucumbers
And I buy 3 apples
And I buy 4 diamonds
But you need different step definition implementations for some cases (data types, actually their regular expressions). In this example, the following cases should be distinguished:
vegetables
fruits
anything else
There are 2 possible solutions how this problem can be mapped into step definitions.
Variant 1: Use Single Method¶
One step definition with a string-based data type is provided in this solution. The step definition implementation contains the logic how to distinguish between the different cases.
# -- FILE: step_matcher.features/steps/one_step.py
from behave import given, when, then
@when("I buy {amount:n} {shop_item:w}")
def step_when_I_buy_shop_item(context, amount, shop_item):
pass # -- HERE comes the logic how to distinguish the cases.
Variant 2: Use Multi-Methods¶
If different data types are needed in the step definitions, another solution may be better. This solution, the multi-methods approach, is described here.
Caution
This solution requires that each case uses a different regular expression for each data type (including the else-case). Otherwise, the step matcher algorithm will not be able to distinguish these cases.
Provide the Step Definitions¶
# file:step_matcher.features/steps/step_multi_methods.py
# ----------------------------------------------------------------------------
# STEPS:
# ----------------------------------------------------------------------------
from behave import given, when, then
@given(u"I go to a shop")
def step_given_I_go_to_a_shop(context):
context.shop = Shop()
context.shopping_cart = [ ]
# -- STEP-ORDERING-IMPORTANT: Else step must be last.
@when(u"I buy {amount:n} {vegetable:Vegetable}")
def step_when_I_buy_vegetable(context, amount, vegetable):
price = context.shop.calculate_price_for_vegetable(vegetable, amount)
context.shopping_cart.append((vegetable, amount, price))
@when(u"I buy {amount:n} {fruit:Fruit}")
def step_when_I_buy_fruit(context, amount, fruit):
price = context.shop.calculate_price_for_fruit(fruit, amount)
context.shopping_cart.append((fruit, amount, price))
@when(u"I buy {amount:n} {anything_else:w}")
def step_when_I_buy_anything_else(context, amount, anything_else):
price = context.shop.calculate_price_for(anything_else, amount)
context.shopping_cart.append((anything_else, amount, price))
Define the Data Types¶
# file:step_matcher.features/steps/step_multi_methods.py
# ------------------------------------------------------------------------
# USER-DEFINED TYPES:
# ------------------------------------------------------------------------
from behave import register_type
from parse_type import TypeBuilder
parse_vegetable = TypeBuilder.make_choice(["cucumbers", "lettuce"])
register_type(Vegetable=parse_vegetable)
parse_fruit = TypeBuilder.make_choice(["apples", "pears"])
register_type(Fruit=parse_fruit)
Run the Test¶
Now we run this example with behave
:
$ behave ../step_matcher.features/use_multi_methods.feature Feature: Use Multi-Methods in Step Definitions # ../step_matcher.features/use_multi_methods.feature:1 Scenario: # ../step_matcher.features/use_multi_methods.feature:2 Given I go to a shop # ../step_matcher.features/steps/step_multi_methods.py:60 When I buy 2 cucumbers # ../step_matcher.features/steps/step_multi_methods.py:66 And I buy 3 apples # ../step_matcher.features/steps/step_multi_methods.py:71 And I buy 4 diamonds # ../step_matcher.features/steps/step_multi_methods.py:76 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 4 steps passed, 0 failed, 0 skipped, 0 undefined Took 0m0.000s
Note
Notice the difference in line numbers for each step. Each step matches a different step definition (implementation).
The Complete Picture¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | # file:step_matcher.features/steps/step_multi_methods.py
# -*- coding: UTF-8 -*-
"""
Feature: Use Multi-Methods in Step Definitions
Scenario:
Given I go to a shop
When I buy 2 cucumbers
And I buy 3 apples
And I buy 4 diamonds
"""
# @mark.domain_model
# ------------------------------------------------------------------------
# DOMAIN MODEL:
# ------------------------------------------------------------------------
class Shop(object):
vegetable_price_list = {
"cucumbers": 0.2, # Dollars per piece.
"lettuce": 0.8, # Dollars per piece.
}
fruit_price_list = {
"apples": 0.5, # Dollars per piece.
"pears": 0.6, # Dollars per piece.
}
common_price_list = {
"diamonds": 1000. # Dollars for one with 10 karat (only 1 size).
}
def calculate_price_for_fruit(self, fruit, amount):
price_per_unit = self.fruit_price_list[fruit]
return price_per_unit*amount
def calculate_price_for_vegetable(self, vegetable, amount):
price_per_unit = self.vegetable_price_list[vegetable]
return price_per_unit*amount
def calculate_price_for(self, shop_item, amount):
price_per_unit = self.common_price_list[shop_item]
return price_per_unit*amount
# @mark.user_defined_types
# ------------------------------------------------------------------------
# USER-DEFINED TYPES:
# ------------------------------------------------------------------------
from behave import register_type
from parse_type import TypeBuilder
parse_vegetable = TypeBuilder.make_choice(["cucumbers", "lettuce"])
register_type(Vegetable=parse_vegetable)
parse_fruit = TypeBuilder.make_choice(["apples", "pears"])
register_type(Fruit=parse_fruit)
# @mark.steps
# ----------------------------------------------------------------------------
# STEPS:
# ----------------------------------------------------------------------------
from behave import given, when, then
@given(u"I go to a shop")
def step_given_I_go_to_a_shop(context):
context.shop = Shop()
context.shopping_cart = [ ]
# -- STEP-ORDERING-IMPORTANT: Else step must be last.
@when(u"I buy {amount:n} {vegetable:Vegetable}")
def step_when_I_buy_vegetable(context, amount, vegetable):
price = context.shop.calculate_price_for_vegetable(vegetable, amount)
context.shopping_cart.append((vegetable, amount, price))
@when(u"I buy {amount:n} {fruit:Fruit}")
def step_when_I_buy_fruit(context, amount, fruit):
price = context.shop.calculate_price_for_fruit(fruit, amount)
context.shopping_cart.append((fruit, amount, price))
@when(u"I buy {amount:n} {anything_else:w}")
def step_when_I_buy_anything_else(context, amount, anything_else):
price = context.shop.calculate_price_for(anything_else, amount)
context.shopping_cart.append((anything_else, amount, price))
|