Tutorial 2: Natural Language

Goal

Use natural language when writing tests.

Hint

This example is based on the Ninja Survival Rate examples from [SecretNinja10].

Write the Feature Test

# file:features/tutorial02_natural_language.feature
Feature: Fight or Flight (Natural Language, tutorial02)

    In order to increase the ninja survival rate,
    As a ninja commander
    I want my ninjas to decide whether to take on an opponent
    based on their skill levels.

    Scenario: Weaker opponent
        Given the ninja has a third level black-belt
        When attacked by a samurai
        Then the ninja should engage the opponent

    Scenario: Stronger opponent
        Given the ninja has a third level black-belt
        When attacked by Chuck Norris
        Then the ninja should run for his life

Provide the Test Automation

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

@given('the ninja has a {achievement_level}')
def step_the_ninja_has_a(context, achievement_level):
    context.ninja_fight = NinjaFight(achievement_level)

@when('attacked by a {opponent_role}')
def step_attacked_by_a(context, opponent_role):
    context.ninja_fight.opponent = opponent_role

@when('attacked by {opponent}')
def step_attacked_by(context, opponent):
    context.ninja_fight.opponent = opponent

@then('the ninja should {reaction}')
def step_the_ninja_should(context, reaction):
    assert_that(reaction, equal_to(context.ninja_fight.decision()))


Provide the Domain Model

Normally, the domain model is the

  • class-under-test (CUT)

  • subsystem-under-test

  • system-under-test (SUT)

It contains the business logic that describes the behaviour of the system. The thin test automation layer from above (step definitions) just interacts with it. The domain model normally preexists (in another Python module/package) and you do not have to write it.

# file:features/steps/step_tutorial02.py
# ----------------------------------------------------------------------------
# PROBLEM DOMAIN:
# ----------------------------------------------------------------------------
class NinjaFight(object):
    """
    Domain model for ninja fights.
    """
    # pylint: disable=R0903

    def __init__(self, with_ninja_level=None):
        self.with_ninja_level = with_ninja_level
        self.opponent = None

    def decision(self):
        """
        Business logic how a Ninja should react to increase his survival rate.
        """
        assert self.with_ninja_level is not None
        assert self.opponent is not None
        if self.opponent == "Chuck Norris":
            return "run for his life"
        if "black-belt" in self.with_ninja_level:
            return "engage the opponent"
        else:
            return "run for his life"

Run the Feature Test

When you run the feature file from above:

$ behave ../features/tutorial02_natural_language.feature
Feature: Fight or Flight (Natural Language, tutorial02)   # ../features/tutorial02_natural_language.feature:1
  In order to increase the ninja survival rate,
  As a ninja commander
  I want my ninjas to decide whether to take on an opponent
  based on their skill levels.

  Scenario: Weaker opponent                      # ../features/tutorial02_natural_language.feature:8
    Given the ninja has a third level black-belt # ../features/steps/step_tutorial02.py:57
    When attacked by a samurai                   # ../features/steps/step_tutorial02.py:61
    Then the ninja should engage the opponent    # ../features/steps/step_tutorial02.py:69

  Scenario: Stronger opponent                    # ../features/tutorial02_natural_language.feature:13
    Given the ninja has a third level black-belt # ../features/steps/step_tutorial02.py:57
    When attacked by Chuck Norris                # ../features/steps/step_tutorial02.py:65
    Then the ninja should run for his life       # ../features/steps/step_tutorial02.py:69

1 feature passed, 0 failed, 0 skipped
2 scenarios passed, 0 failed, 0 skipped
6 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m0.001s

The Complete Picture

# file:features/steps/step_tutorial02.py
# -*- coding: UTF-8 -*-
# MISSING-DOCSTRING: pylint: disable=C0111
"""
Based on ``behave tutorial``

Feature: Fight or Flight (Natural Language)
    In order to increase the ninja survival rate,   #< Business goal
    As a ninja commander                            #< Role
    I want my ninjas to decide whether to take on an opponent   #< Benefit
        based on their skill levels

    Scenario: Weaker opponent
        Given the ninja has a third level black-belt
        When attacked by a samurai
        Then the ninja should engage the opponent

    Scenario: Stronger opponent
        Given the ninja has a third level black-belt
        When attacked by Chuck Norris
        Then the ninja should run for his life
"""

# @mark.domain_model
# ----------------------------------------------------------------------------
# PROBLEM DOMAIN:
# ----------------------------------------------------------------------------
class NinjaFight(object):
    """
    Domain model for ninja fights.
    """
    # pylint: disable=R0903

    def __init__(self, with_ninja_level=None):
        self.with_ninja_level = with_ninja_level
        self.opponent = None

    def decision(self):
        """
        Business logic how a Ninja should react to increase his survival rate.
        """
        assert self.with_ninja_level is not None
        assert self.opponent is not None
        if self.opponent == "Chuck Norris":
            return "run for his life"
        if "black-belt" in self.with_ninja_level:
            return "engage the opponent"
        else:
            return "run for his life"

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

@given('the ninja has a {achievement_level}')
def step_the_ninja_has_a(context, achievement_level):
    context.ninja_fight = NinjaFight(achievement_level)

@when('attacked by a {opponent_role}')
def step_attacked_by_a(context, opponent_role):
    context.ninja_fight.opponent = opponent_role

@when('attacked by {opponent}')
def step_attacked_by(context, opponent):
    context.ninja_fight.opponent = opponent

@then('the ninja should {reaction}')
def step_the_ninja_should(context, reaction):
    assert_that(reaction, equal_to(context.ninja_fight.decision()))