Cardinality: One or More (List of Type)¶
Sometimes a solution is needed where a list of one or more items needs to be parsed. Initially, this should be a comma-separated list, like:
Scenario:
When I meet Alice
And I meet Alice, Bob, Charly
Then, a list should be processed that is separated by the word “and”, like:
Scenario:
When I meet Alice and Bob and Charly
Feature Example¶
# file:datatype.features/cardinality.one_or_more.feature
Feature: Data Type with Cardinality one or more (MANY, List<T>)
Scenario: Many list, comma-separated
Given I go to a meeting
When I meet Alice, Bob, Dodo
And I meet Charly
Then the following persons are present:
| name |
| Alice |
| Bob |
| Charly |
| Dodo |
Scenario: Many list with list-separator "and"
Given I go to a meeting
When I meet Alice and Bob and Charly
Then the following persons are present:
| name |
| Alice |
| Bob |
| Charly |
Define the Data Type¶
# file:datatype.features/steps/step_cardinality_one_or_more.py
# ------------------------------------------------------------------------
# USER-DEFINED TYPES:
# ------------------------------------------------------------------------
from behave import register_type
from parse_type import TypeBuilder
company_persons = [ "Alice", "Bob", "Charly", "Dodo" ]
parse_person = TypeBuilder.make_choice(company_persons)
register_type(Person=parse_person)
# -- MANY-TYPE: Persons := list<Person> with list-separator = "and"
# parse_persons = TypeBuilder.with_one_or_more(parse_person, listsep="and")
parse_persons = TypeBuilder.with_many(parse_person, listsep="and")
register_type(PersonAndMore=parse_persons)
# -- NEEDED-UNTIL: parse_type.cfparse.Parser is used by behave.
# parse_persons2 = TypeBuilder.with_many(parse_person)
# type_dict = {"Person+": parse_persons2}
# register_type(**type_dict)
Note
The TypeBuilder.with_many()
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 (“Person”).
Provide the Step Definitions¶
# file:datatype.features/steps/step_cardinality_one_or_more.py
# ----------------------------------------------------------------------------
# STEPS:
# ----------------------------------------------------------------------------
from behave import given, when, then
# -- MANY-VARIANT 1: Use Cardinality field in parse expression (comma-separated)
@when('I meet {persons:Person+}')
def step_when_I_meet_persons(context, persons):
for person in persons:
context.meeting.persons.add(person)
# -- MANY-VARIANT 2: Use special many data type ("and"-separated)
@when('I meet {persons:PersonAndMore}')
def step_when_I_meet_person_and_more(context, persons):
for person in persons:
context.meeting.persons.add(person)
# ----------------------------------------------------------------------------
# MORE STEPS:
# ----------------------------------------------------------------------------
from hamcrest import assert_that, contains
@given('I go to a meeting')
def step_given_I_go_to_meeting(context):
context.meeting = Meeting()
@then('the following persons are present')
def step_following_persons_are_present(context):
assert context.table, "table<Person> is required"
actual_persons = sorted(context.meeting.persons)
expected_persons = [ row["name"] for row in context.table ]
# -- LIST-COMPARISON:
assert_that(actual_persons, contains(*expected_persons))
Run the Test¶
Now we run this example with behave
:
$ behave ../datatype.features/cardinality.one_or_more.feature Feature: Data Type with Cardinality one or more (MANY, List<T>) # ../datatype.features/cardinality.one_or_more.feature:1 Scenario: Many list, comma-separated # ../datatype.features/cardinality.one_or_more.feature:3 Given I go to a meeting # ../datatype.features/steps/step_cardinality_one_or_more.py:79 When I meet Alice, Bob, Dodo # ../datatype.features/steps/step_cardinality_one_or_more.py:63 And I meet Charly # ../datatype.features/steps/step_cardinality_one_or_more.py:63 Then the following persons are present # ../datatype.features/steps/step_cardinality_one_or_more.py:83 | name | | Alice | | Bob | | Charly | | Dodo | Scenario: Many list with list-separator "and" # ../datatype.features/cardinality.one_or_more.feature:14 Given I go to a meeting # ../datatype.features/steps/step_cardinality_one_or_more.py:79 When I meet Alice and Bob and Charly # ../datatype.features/steps/step_cardinality_one_or_more.py:69 Then the following persons are present # ../datatype.features/steps/step_cardinality_one_or_more.py:83 | name | | Alice | | Bob | | Charly | 1 feature passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 7 steps passed, 0 failed, 0 skipped, 0 undefined Took 0m0.002s
The Complete Picture¶
# file:datatype.features/steps/step_cardinality_one_or_more.py
# -*- coding: UTF-8 -*-
"""
Feature: Data Type with Cardinality one or more (MANY, List<T>)
Scenario:
Given I go to a meeting
When I meet Alice, Bob, Charly
And I meet Dodo
Then the following persons are present:
| name |
| Alice |
| Bob |
| Charly |
| Dodo |
Scenario: Many list with list-separator "and"
Given I go to a meeting
When I meet Alice and Bob and Charly
Then the following persons are present:
| name |
| Bob |
| Alice |
| Charly |
"""
# ----------------------------------------------------------------------------
# DOMAIN MODEL:
# ----------------------------------------------------------------------------
class Meeting(object):
def __init__(self):
self.persons = set()
# @mark.user_defined_types
# ------------------------------------------------------------------------
# USER-DEFINED TYPES:
# ------------------------------------------------------------------------
from behave import register_type
from parse_type import TypeBuilder
company_persons = [ "Alice", "Bob", "Charly", "Dodo" ]
parse_person = TypeBuilder.make_choice(company_persons)
register_type(Person=parse_person)
# -- MANY-TYPE: Persons := list<Person> with list-separator = "and"
# parse_persons = TypeBuilder.with_one_or_more(parse_person, listsep="and")
parse_persons = TypeBuilder.with_many(parse_person, listsep="and")
register_type(PersonAndMore=parse_persons)
# -- NEEDED-UNTIL: parse_type.cfparse.Parser is used by behave.
# parse_persons2 = TypeBuilder.with_many(parse_person)
# type_dict = {"Person+": parse_persons2}
# 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('I meet {persons:Person+}')
def step_when_I_meet_persons(context, persons):
for person in persons:
context.meeting.persons.add(person)
# -- MANY-VARIANT 2: Use special many data type ("and"-separated)
@when('I meet {persons:PersonAndMore}')
def step_when_I_meet_person_and_more(context, persons):
for person in persons:
context.meeting.persons.add(person)
# ----------------------------------------------------------------------------
# MORE STEPS:
# ----------------------------------------------------------------------------
from hamcrest import assert_that, contains
@given('I go to a meeting')
def step_given_I_go_to_meeting(context):
context.meeting = Meeting()
@then('the following persons are present')
def step_following_persons_are_present(context):
assert context.table, "table<Person> is required"
actual_persons = sorted(context.meeting.persons)
expected_persons = [ row["name"] for row in context.table ]
# -- LIST-COMPARISON:
assert_that(actual_persons, contains(*expected_persons))