Cardinality: Zero or More (List of Type)¶
The solution to this problem is basically the same like with Cardinality: One or More (List of Type). Note that the case for zero or more items is not so often needed.
Initially, a comma-separated list is processed, like:
Scenario:
When I paint with red, green
Next, a list that is separated with the word “and” is processed, like:
Scenario:
When I paint with red and green
Feature Example¶
# file:datatype.features/cardinality.zero_or_more.feature
Feature: Data Type with Cardinality zero or more (MANY0, List<T>)
Scenario: Empty list, comma-separated
Given I am a painter
When I paint with
Then no colors are used
Scenario: List with one item, comma-separated
Given I am a painter
When I paint with blue
Then the following colors are used:
| color |
| blue |
Scenario: Many list, comma-separated
Given I am a painter
When I paint with red, green
Then the following colors are used:
| color |
| red |
| green |
Scenario: Many list with list-separator "and"
Given I am a painter
When I paint with red and green and blue
Then the following colors are used:
| color |
| red |
| green |
| blue |
Define the Data Type¶
# file:datatype.features/steps/step_cardinality_zero_or_more.py
# ------------------------------------------------------------------------
# USER-DEFINED TYPES:
# ------------------------------------------------------------------------
from behave import register_type
from parse_type import TypeBuilder
def slurp_space(text):
return text
slurp_space.pattern = r"\s*"
register_type(slurp_space=slurp_space)
parse_color = TypeBuilder.make_choice([ "red", "green", "blue", "yellow" ])
register_type(Color=parse_color)
# -- MANY-TYPE: Persons := list<Person> with list-separator = "and"
# parse_colors = TypeBuilder.with_many0(parse_color, listsep="and")
parse_colors0A= TypeBuilder.with_zero_or_more(parse_color, listsep="and")
register_type(OptionalColorAndMore=parse_colors0A)
# -- NEEDED-UNTIL: parse_type.cfparse.Parser is used by behave.
# parse_colors0C = TypeBuilder.with_zero_or_more(parse_color)
# type_dict = {"Color*": parse_colors0C}
# register_type(**type_dict)
Note
The TypeBuilder.with_zero_and_more()
function performs the magic.
It computes a regular expression pattern for the list of items.
Then it generates a type-converter function that processes the list of
items by using the type-converter for one item (“Color”).
Provide the Step Definitions¶
# file:datatype.features/steps/step_cardinality_zero_or_more.py
# ----------------------------------------------------------------------------
# STEPS:
# ----------------------------------------------------------------------------
from behave import given, when, then
# -- MANY-VARIANT 1: Use Cardinality field in parse expression (comma-separated)
@when(u'I paint with{:slurp_space}{colors:Color*}')
def step_when_I_paint_with_colors(context, _, colors):
for color in colors:
context.used_colors.add(color)
# -- MANY-VARIANT 2: Use special many data type ("and"-separated)
@when(u'I paint with{:slurp_space}{colors:OptionalColorAndMore}')
def step_when_I_paint_with_color_and_more(context, _, colors):
for color in colors:
context.used_colors.add(color)
# ----------------------------------------------------------------------------
# MORE STEPS:
# ----------------------------------------------------------------------------
from hamcrest import assert_that, contains, has_length
@given('I am a painter')
def step_given_I_am_a_painter(context):
context.used_colors = set()
@then('no colors are used')
def step_then_no_colors_are_used(context):
assert_that(context.used_colors, has_length(0))
@then('the following colors are used')
def step_then_following_colors_are_used(context):
assert context.table, "table<color> is required"
used_colors = sorted(context.used_colors)
expected_colors = [ row[0] for row in context.table ]
# -- LIST-COMPARISON:
assert_that(used_colors, contains(*sorted(expected_colors)))
Run the Test¶
Now we run this example with behave
:
$ behave ../datatype.features/cardinality.zero_or_more.feature Feature: Data Type with Cardinality zero or more (MANY0, List<T>) # ../datatype.features/cardinality.zero_or_more.feature:1 Scenario: Empty list, comma-separated # ../datatype.features/cardinality.zero_or_more.feature:3 Given I am a painter # ../datatype.features/steps/step_cardinality_zero_or_more.py:73 When I paint with # ../datatype.features/steps/step_cardinality_zero_or_more.py:56 Then no colors are used # ../datatype.features/steps/step_cardinality_zero_or_more.py:77 Scenario: List with one item, comma-separated # ../datatype.features/cardinality.zero_or_more.feature:8 Given I am a painter # ../datatype.features/steps/step_cardinality_zero_or_more.py:73 When I paint with blue # ../datatype.features/steps/step_cardinality_zero_or_more.py:56 Then the following colors are used # ../datatype.features/steps/step_cardinality_zero_or_more.py:81 | color | | blue | Scenario: Many list, comma-separated # ../datatype.features/cardinality.zero_or_more.feature:15 Given I am a painter # ../datatype.features/steps/step_cardinality_zero_or_more.py:73 When I paint with red, green # ../datatype.features/steps/step_cardinality_zero_or_more.py:56 Then the following colors are used # ../datatype.features/steps/step_cardinality_zero_or_more.py:81 | color | | red | | green | Scenario: Many list with list-separator "and" # ../datatype.features/cardinality.zero_or_more.feature:23 Given I am a painter # ../datatype.features/steps/step_cardinality_zero_or_more.py:73 When I paint with red and green and blue # ../datatype.features/steps/step_cardinality_zero_or_more.py:62 Then the following colors are used # ../datatype.features/steps/step_cardinality_zero_or_more.py:81 | color | | red | | green | | blue | 1 feature passed, 0 failed, 0 skipped 4 scenarios passed, 0 failed, 0 skipped 12 steps passed, 0 failed, 0 skipped, 0 undefined Took 0m0.003s
The Complete Picture¶
# file:datatype.features/steps/step_cardinality_zero_or_more.py
# -*- coding: UTF-8 -*-
"""
Feature: Data Type with Cardinality zero or more (MANY0, List<T>)
Scenario: Many list, comma-separated
Given I am a painter
When I paint with red, green
Then the following colors are used:
| color |
| red |
| green |
Scenario: Many list with list-separator "and"
Given I am a painter
When I paint with red and green and blue
Then the following colors are used:
| color |
| red |
| green |
| blue |
"""
# @mark.user_defined_types
# ------------------------------------------------------------------------
# USER-DEFINED TYPES:
# ------------------------------------------------------------------------
from behave import register_type
from parse_type import TypeBuilder
def slurp_space(text):
return text
slurp_space.pattern = r"\s*"
register_type(slurp_space=slurp_space)
parse_color = TypeBuilder.make_choice([ "red", "green", "blue", "yellow" ])
register_type(Color=parse_color)
# -- MANY-TYPE: Persons := list<Person> with list-separator = "and"
# parse_colors = TypeBuilder.with_many0(parse_color, listsep="and")
parse_colors0A= TypeBuilder.with_zero_or_more(parse_color, listsep="and")
register_type(OptionalColorAndMore=parse_colors0A)
# -- NEEDED-UNTIL: parse_type.cfparse.Parser is used by behave.
# parse_colors0C = TypeBuilder.with_zero_or_more(parse_color)
# type_dict = {"Color*": parse_colors0C}
# register_type(**type_dict)
# @mark.steps
# ----------------------------------------------------------------------------
# STEPS:
# ----------------------------------------------------------------------------
from behave import given, when, then
# -- MANY-VARIANT 1: Use Cardinality field in parse expression (comma-separated)
@when(u'I paint with{:slurp_space}{colors:Color*}')
def step_when_I_paint_with_colors(context, _, colors):
for color in colors:
context.used_colors.add(color)
# -- MANY-VARIANT 2: Use special many data type ("and"-separated)
@when(u'I paint with{:slurp_space}{colors:OptionalColorAndMore}')
def step_when_I_paint_with_color_and_more(context, _, colors):
for color in colors:
context.used_colors.add(color)
# ----------------------------------------------------------------------------
# MORE STEPS:
# ----------------------------------------------------------------------------
from hamcrest import assert_that, contains, has_length
@given('I am a painter')
def step_given_I_am_a_painter(context):
context.used_colors = set()
@then('no colors are used')
def step_then_no_colors_are_used(context):
assert_that(context.used_colors, has_length(0))
@then('the following colors are used')
def step_then_following_colors_are_used(context):
assert context.table, "table<color> is required"
used_colors = sorted(context.used_colors)
expected_colors = [ row[0] for row in context.table ]
# -- LIST-COMPARISON:
assert_that(used_colors, contains(*sorted(expected_colors)))