Cardinality: Zero or One (Optional)¶
There are some cases, when a text part may be present or not. Therefore, this text part is an optional and has cardinality zero or one (0..1).
The parse_type.TypeBuilder
can be used to compute the type with
cardinality zero or one based on data type with cardinality one.
See also
Use Optional Part in Step Definitions for a simpler solution to this problem by using the cardinality field in parse expressions.
Feature Example¶
Assuming you want to write something like this:
# file:datatype.features/cardinality.zero_or_one.feature
Feature: Data Type with Cardinality 0..1 (Optional Part)
Scenario: Case1 "When attacked by a ..."
Given the ninja has a black-belt
When attacked by a samurai
Scenario: Case2 "When attacked by ..."
Given the ninja has a black-belt
When attacked by Chuck Norris
# -- DESCRIPTION:
# "When attacked by ...": Once with "a ", once without it.
# Only one step should be used.
Define the Data Type¶
# file:datatype.features/steps/step_cardinality_zero_or_one.py
# ------------------------------------------------------------------------
# USER-DEFINED TYPES:
# ------------------------------------------------------------------------
from behave import register_type
from parse_type import TypeBuilder
import parse
@parse.with_pattern(r"a\s+")
def parse_word_a(text):
"""Type converter for "a " (followed by one/more spaces)."""
return text.strip()
# -- SAME:
# parse_optional_word_a = TypeBuilder.with_zero_or_one(parse_word_a)
parse_optional_word_a = TypeBuilder.with_optional(parse_word_a)
register_type(optional_a_=parse_optional_word_a)
Note
The TypeBuilder.with_optional()
function performs the magic.
It computes a regular expression pattern for the given choice of
words/strings and stores them in parse_optional_word_a.pattern
attribute.
Provide the Step Definitions¶
# file:datatype.features/steps/step_cardinality_zero_or_one.py
# ----------------------------------------------------------------------------
# STEPS:
# ----------------------------------------------------------------------------
from behave import given, when, then
from hamcrest import assert_that, equal_to, is_in
# -- OPTIONAL-PART: {:optional_a_}
# By using data type with cardinality zero or one (0..1, optional).
@when('attacked by {:optional_a_}{opponent}')
def step_attacked_by(context, a_, opponent):
context.ninja_fight.opponent = opponent
# -- VERIFY: Optional part feature.
assert_that(a_, is_in(["a", None]))
assert_that(opponent, is_in(["Chuck Norris", "samurai"]))
# ----------------------------------------------------------------------------
# MORE STEPS:
# ----------------------------------------------------------------------------
from ninja_fight import NinjaFight
@given('the ninja has a {achievement_level}')
def step_the_ninja_has_a(context, achievement_level):
context.ninja_fight = NinjaFight(achievement_level)
@then('the ninja should {reaction}')
def step_the_ninja_should(context, reaction):
assert_that(reaction, equal_to(context.ninja_fight.decision()))
Run the Test¶
Now we run this example with behave
:
$ behave ../datatype.features/cardinality.zero_or_one.feature Feature: Data Type with Cardinality 0..1 (Optional Part) # ../datatype.features/cardinality.zero_or_one.feature:1 Scenario: Case1 "When attacked by a ..." # ../datatype.features/cardinality.zero_or_one.feature:3 Given the ninja has a black-belt # ../datatype.features/steps/step_cardinality_zero_or_one.py:54 When attacked by a samurai # ../datatype.features/steps/step_cardinality_zero_or_one.py:42 Scenario: Case2 "When attacked by ..." # ../datatype.features/cardinality.zero_or_one.feature:7 Given the ninja has a black-belt # ../datatype.features/steps/step_cardinality_zero_or_one.py:54 When attacked by Chuck Norris # ../datatype.features/steps/step_cardinality_zero_or_one.py:42 1 feature passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 4 steps passed, 0 failed, 0 skipped, 0 undefined Took 0m0.002s
The Complete Picture¶
# file:datatype.features/steps/step_cardinality_zero_or_one.py
# -*- coding: UTF-8 -*-
"""
Feature: Use Optional Part in Step Definitions
Scenario: Case 1 with "a "
Given ...
When attacked by a samurai
Scenario: Case 2 without "a "
Given ...
When attacked by Chuck Norris
"""
# @mark.user_defined_types
# ------------------------------------------------------------------------
# USER-DEFINED TYPES:
# ------------------------------------------------------------------------
from behave import register_type
from parse_type import TypeBuilder
import parse
@parse.with_pattern(r"a\s+")
def parse_word_a(text):
"""Type converter for "a " (followed by one/more spaces)."""
return text.strip()
# -- SAME:
# parse_optional_word_a = TypeBuilder.with_zero_or_one(parse_word_a)
parse_optional_word_a = TypeBuilder.with_optional(parse_word_a)
register_type(optional_a_=parse_optional_word_a)
# @mark.steps
# ----------------------------------------------------------------------------
# STEPS:
# ----------------------------------------------------------------------------
from behave import given, when, then
from hamcrest import assert_that, equal_to, is_in
# -- OPTIONAL-PART: {:optional_a_}
# By using data type with cardinality zero or one (0..1, optional).
@when('attacked by {:optional_a_}{opponent}')
def step_attacked_by(context, a_, opponent):
context.ninja_fight.opponent = opponent
# -- VERIFY: Optional part feature.
assert_that(a_, is_in(["a", None]))
assert_that(opponent, is_in(["Chuck Norris", "samurai"]))
# ----------------------------------------------------------------------------
# MORE STEPS:
# ----------------------------------------------------------------------------
from ninja_fight import NinjaFight
@given('the ninja has a {achievement_level}')
def step_the_ninja_has_a(context, achievement_level):
context.ninja_fight = NinjaFight(achievement_level)
@then('the ninja should {reaction}')
def step_the_ninja_should(context, reaction):
assert_that(reaction, equal_to(context.ninja_fight.decision()))