Thursday, July 28, 2016

Getting Started with Behavior Testing in Python with Behave_part1

Learn how to write behavioral tests for your next Python application using the Behave library.

Introduction

Behavior testing simply means that we should test how an application behaves in certain situations. Often the behavior is given to us developers by our customers. They describe the functionality of an application, and we write code to meet their specifications. Behavioral tests are a tool to formalize their requirements into tests. This leads naturally to behavior-driven development (BDD).

After completing this tutorial, you should be able to:
  • Explain the benefits of behavior testing
  • Explain the "given", "when", and "then" phases of Behave
  • Write basic behavioral tests using Behave
  • Write parameterized behavioral tests using Behave
Prerequisites

Before starting, make sure you have the following installed:
Setting Up Your Environment

This tutorial will walk you through writing tests for and coding a feature of a Twenty-One (or "Blackjack") game. Specifically, we'll be testing the logic for the dealer. To get started, create a root directory where your code will go, and then create the following directories and blank files:
.
├── features
│   ├── dealer.feature
│   └── steps
│       └── steps.py
└── twentyone.py

Here's a brief explanation of the files:
  • dealer.feature: The written out tests for the dealer feature.
  • steps.py: The code that runs the tests in dealer.feature.
  • twentyone.py: The implementation code for the dealer feature.
Writing Your First Test
Although behavioral tests do not require test-driven development, the two methodologies go hand-in-hand. We'll approach this problem from a test-driven perspective, so instead of jumping to code, we'll start with the tests.

Writing the Scenario
Open dealer.feature and add the following first line:

Feature: The dealer for the game of 21

This line describes the feature. In a large application, you would have many features. Next, we'll add a test. The first test will be simple — when the round starts, the dealer should deal itself two cards. The word Behave uses to define a test is "Scenario", so go ahead and add the following line:

Scenario: Deal initial cards

Before we write more, we need to understand the three phases of a basic Behave test: "Given", "When", and "Then". "Given" initializes a state, "When" describes an action, and "Then" states the expected outcome. For this test, our state is a new dealer object, the action is the round starting, and the expected outcome is that the dealer has two cards. Here's how this is translated into a Behave test:

Scenario: Deal initial cards
  Given a dealer
  When the round starts
  Then the dealer gives itself two cards

Notice that the three phases read like a normal English sentence. You should strive for this when writing behavioral tests because they are easily readable by anyone working in the code base.

Now to see how Behave works, simply open a terminal in the root directory of your code and run the following command:

behave

You should see this output:

Feature: The dealer for the game of 21     # features/dealer.feature:1

  Scenario: Deal initial cards                    # features/dealer.feature:3
    Given a dealer                                      # None
    When the round starts                          # None
    Then the dealer gives itself two cards # None

Failing scenarios:
  features/dealer.feature:3  Deal initial cards

0 features passed, 1 failed, 0 skipped
0 scenarios passed, 1 failed, 0 skipped
0 steps passed, 0 failed, 0 skipped, 3 undefined
Took 0m0.000s

You can implement step definitions for undefined steps with these snippets:
[ The rest of output removed for brevity ]

The key part here is that we have one failing scenario (and therefore a failing feature) that we need to fix. Below that, Behave suggests how to implement steps. You can think of a step as a task for Behave to execute. Each phase ("given", "when", and "then") are all implemented as steps.

Writing the Steps

The steps that Behave runs are written in Python and they are the link between the descriptive tests in .feature files and the actual application code. Go ahead and open steps.py and add the following imports:

from behave import *
from twentyone import *

Behave steps use annotations that match the names of the phases. This is the first step as described in the scenario:

@given('a dealer')
def step_impl(context):
    context.dealer = Dealer()

It's important to notice that the text inside of the annotation matches the scenario text exactly. If it doesn't match, the test cannot run.

The context object is passed from step to step, and it is where we can store information to be used by other steps. Since this step is a "given", we need to initialize our state. We do that by creating a Dealer object, and attaching that object to the context. If you run behave again, you'll see the test fails, but now for a different reason: We haven't defined the Dealer class yet! Again, we have a failing test that is "driving" us to do work.

Now we will open twentyone.py and create a Dealer class:

class Dealer():
    pass

Run behave once again to verify that we fixed the last error we saw, but that the scenario still fails because the "when" and "then" steps are not implemented. From here on, the tutorial will not explicitly state when you should run behave. But remember, the cycle is to write a test, see that it fails, and then write code to make the test pass.

Here are the next steps to add to steps.py:

@when('the round starts')
def step_impl(context):
    context.dealer.new_round()
@then('the dealer gives itself two cards')
def step_impl(context):
    assert (len(context.dealer.hand) == 2)

Again, the annotation text matches the text in the scenario exactly. In the "when" step, we have access to the dealer created in "given" and we can now call a method on that object. Finally, in the "then" step, we still have access to the dealer, and we assert that the dealer has two cards in its hand.

We defined two new pieces of code that need to be implemented: new_round() and hand. Switch back to twentyone.py and add the following to the Dealer class:

class Dealer():
    def __init__(self):
        self.hand = []

    def new_round(self):
        self.hand = [_next_card(), _next_card()]

The _next_card() function will be defined as a top-level function of the module, along with a definition of the cards. At the top of the file, add the following:

import random
_cards = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
def _next_card():
    return random.choice(_cards)

Remember that random is not secure and should not be used in a real implementation of this game, but for this tutorial it will be fine.

If you run behave now, you should see that the test passes:

Feature: The dealer for the game of 21     # features/dealer.feature:1
  Scenario: Deal initial cards                    # features/dealer.feature:3
    Given a dealer                                      # features/steps/steps.py:5 0.000s
    When the round starts                          # features/steps/steps.py:9 0.000s
    Then the dealer gives itself two cards # features/steps/steps.py:14 0.000s
1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 0 skipped
3 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m0.000s

Source :semaphoreci .(continue )
If you feel useful for you and for everyone, please share it!
Suggest for you:








No comments:

Post a Comment