Tutorial 10: User-defined Data Type

Goal

Show how user-defined data types can be used in step parameters.

User-defined data types simplify the processing in step definitions. The string parameters are automatically parsed and converted into specific data types.

Note

Besides conversion into a user-defined type, this mechanism can also be used for text transformations that occurs before the parameter is handed to the step definition function.

Write the Feature Test

# file:features/tutorial10_step_usertype.feature
Feature: User-Defined Datatype as Step Parameter (tutorial10)

  As a test writer
  I want that a step parameter is converted into a specific datatype
  to simplify the programming of the step definition body.

  Scenario Outline: Calculator
    Given I have a calculator
    When I add "<x>" and "<y>"
    Then the calculator returns "<sum>"

    Examples:
        |  x  |  y | sum |
        |  1  |  1 |  2  |
        |  1  |  2 |  3  |
        |  2  |  1 |  3  |
        |  2  |  7 |  9  |

Provide the Test Automation

First you need to provide the type converter for Number and register it:

# file:features/steps/step_tutorial10.py
# ----------------------------------------------------------------------------
# USER-DEFINED TYPES:
# ----------------------------------------------------------------------------
from behave import register_type

def parse_number(text):
    """
    Convert parsed text into a number.
    :param text: Parsed text, called by :py:meth:`parse.Parser.parse()`.
    :return: Number instance (integer), created from parsed text.
    """
    return int(text)
# -- REGISTER: User-defined type converter (parse_type).
register_type(Number=parse_number)

Now you can use Number as type in step parameters for the step definitions:

# file:features/steps/step_tutorial10.py
# ----------------------------------------------------------------------------
# STEPS:
# ----------------------------------------------------------------------------
from behave   import given, when, then
from hamcrest import assert_that, equal_to
from calculator import Calculator

@given('I have a calculator')
def step_impl(context):
    context.calculator = Calculator()

@when('I add "{x:Number}" and "{y:Number}"')
def step_impl(context, x, y):
    assert isinstance(x, int)
    assert isinstance(y, int)
    context.calculator.add2(x, y)

@then('the calculator returns "{expected:Number}"')
def step_impl(context, expected):
    assert isinstance(expected, int)
    assert_that(context.calculator.result, equal_to(expected))

Provide the Domain Model

# file:features/steps/calculator.py
# -----------------------------------------------------------------------------
# DOMAIN-MODEL:
# -----------------------------------------------------------------------------
class Calculator(object):

    def __init__(self, value=0):
        self.result = value

    def reset(self):
        self.result = 0

    def add2(self, x, y):
        self.result += (x + y)
        return self.result

Run the Feature Test

When you run the feature file from above:

$ behave ../features/tutorial10_step_usertype.feature
Feature: User-Defined Datatype as Step Parameter (tutorial10)   # ../features/tutorial10_step_usertype.feature:1
  As a test writer
  I want that a step parameter is converted into a specific datatype
  to simplify the programming of the step definition body.

  Scenario Outline: Calculator -- @1.1   # ../features/tutorial10_step_usertype.feature:14
    Given I have a calculator            # ../features/steps/step_tutorial10.py:44
    When I add "1" and "1"               # ../features/steps/step_tutorial10.py:48
    Then the calculator returns "2"      # ../features/steps/step_tutorial10.py:54

  Scenario Outline: Calculator -- @1.2   # ../features/tutorial10_step_usertype.feature:15
    Given I have a calculator            # ../features/steps/step_tutorial10.py:44
    When I add "1" and "2"               # ../features/steps/step_tutorial10.py:48
    Then the calculator returns "3"      # ../features/steps/step_tutorial10.py:54

  Scenario Outline: Calculator -- @1.3   # ../features/tutorial10_step_usertype.feature:16
    Given I have a calculator            # ../features/steps/step_tutorial10.py:44
    When I add "2" and "1"               # ../features/steps/step_tutorial10.py:48
    Then the calculator returns "3"      # ../features/steps/step_tutorial10.py:54

  Scenario Outline: Calculator -- @1.4   # ../features/tutorial10_step_usertype.feature:17
    Given I have a calculator            # ../features/steps/step_tutorial10.py:44
    When I add "2" and "7"               # ../features/steps/step_tutorial10.py:48
    Then the calculator returns "9"      # ../features/steps/step_tutorial10.py:54

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.001s

The Complete Picture

# file:features/steps/step_tutorial10.py
# -*- coding: UTF-8 -*-
"""
Based on ``behave tutorial``

Feature: A Step uses a User-Defined Type as Step Parameter (tutorial10)

  Scenario Outline: Calculator
    Given I have a calculator
    When I add "<x>" and "<y>"
    Then the calculator returns "<sum>"

    Examples: Add Numbers
        |  x  |  y | sum |
        |  1  |  1 |  2  |
        |  1  |  2 |  3  |
        |  2  |  1 |  3  |
        |  2  |  7 |  9  |
"""

# @mark.user_defined_types
# ----------------------------------------------------------------------------
# USER-DEFINED TYPES:
# ----------------------------------------------------------------------------
from behave import register_type

def parse_number(text):
    """
    Convert parsed text into a number.
    :param text: Parsed text, called by :py:meth:`parse.Parser.parse()`.
    :return: Number instance (integer), created from parsed text.
    """
    return int(text)
# -- REGISTER: User-defined type converter (parse_type).
register_type(Number=parse_number)

# @mark.steps
# ----------------------------------------------------------------------------
# STEPS:
# ----------------------------------------------------------------------------
from behave   import given, when, then
from hamcrest import assert_that, equal_to
from calculator import Calculator

@given('I have a calculator')
def step_impl(context):
    context.calculator = Calculator()

@when('I add "{x:Number}" and "{y:Number}"')
def step_impl(context, x, y):
    assert isinstance(x, int)
    assert isinstance(y, int)
    context.calculator.add2(x, y)

@then('the calculator returns "{expected:Number}"')
def step_impl(context, expected):
    assert isinstance(expected, int)
    assert_that(context.calculator.result, equal_to(expected))

Note

Predefined Types

behave uses the parse module (inverse of Python string.format) under the hoods to parse parameters in step definitions. This leads to rather simple and readable parse expressions for step parameters.

See also Predefined Data Types in parse for more information. In addition, see also Data Types and User-defined Types for more information on defining and using user-defined data types.