Getting Started With BDD With Calabash and Specta (Part 2)

So back in Part 1 we set-up a simple Quiz App, we added Specta and Calabash to the project and most importantly we managed to code to of the steps from the first feature using an outside-in approach. In Part 2 we are going tocontinue where we left off and finish off the rest of the features. By the end of this post we will have a fully passing suite of automated acceptance tests. To finish off I will run through some of the pitfalls as the complexity of a project grows.

So the next step we need to get working is “Then a question is displayed”. For this to pass we are going to need when you actually press the Show Question button it display a question. We will start by adding a step definition in. In the step_definition/quiz_steps.rb file add the following:

Then(/^a question is displayed$/) do
  results = query "label accessibilityLabel:'question-label'"

  results.each do | result |
  	expect( result['text'].length ).to be > 0
  end
end

Calabash provides a query method and we are using this to look for the accessibility label “question-label”, we then loop through the array of results and validate that the length of the text property is greater than 0. With that in place go ahead and run Cucumber.

$ cucumber -t @wip

Feature: Take a quiz
  As a user
  I want to take a quiz
  So I can improve my general knowledge

  @wip
  Scenario: View quiz question      # features/take_a_quiz.feature:7
    Given I am on the Quiz Screen   # features/step_definitions/quiz_steps.rb:1
    When I view a question          # features/step_definitions/quiz_steps.rb:5
    Then a question is displayed    # features/step_definitions/quiz_steps.rb:9
      expected: > 0
           got:   0 (RSpec::Expectations::ExpectationNotMetError)
      ./features/step_definitions/quiz_steps.rb:13:in `block (2 levels) in <top (required)>'
      ./features/step_definitions/quiz_steps.rb:12:in `each'
      ./features/step_definitions/quiz_steps.rb:12:in `/^a question is displayed$/'
      features/take_a_quiz.feature:10:in `Then a question is displayed'
    And the answer is not displayed # features/take_a_quiz.feature:11

Failing Scenarios:
cucumber features/take_a_quiz.feature:7 # Scenario: View quiz question

1 scenario (1 failed)
4 steps (1 failed, 1 undefined, 2 passed)
0m9.535s

You can implement step definitions for undefined steps with these snippets:

Then(/^the answer is not displayed$/) do
  pending # express the regexp above with the code you wish you had
end

As expected the test fails because when you press the “show question” button it doesn’t show a question. We will now fix this and write some tests using Specta as we go along.

Right-click on the QuizTests group and create a new group View Controller Tests, in the new View Controller Tests group right-click and click New File. Create a new Objective-C File and name the file QuizViewControllerSpec.m, and set the File Type to Empty File.

Open the new QuizViewControllerSpec file and edit the contents of the file to be:

//
//  QuizViewControllerSpec.m
//  Quiz
//
//  Created by Dave Green on 05/05/2015.
//  Copyright (c) 2015 DeveloperDave. All rights reserved.
//


#import "Specta.h"
#import "Expecta.h"

#import "AppDelegate.h"
#import "QuizViewController.h"


@interface QuizViewController (Specs)

@property (nonatomic) int currentQuestionIndex;
@property (weak, nonatomic) IBOutlet UIButton *questionButton;
@property (weak, nonatomic) IBOutlet UILabel *questionLabel;

@property (nonatomic, copy) NSArray *questions;

- (IBAction)showQuestion:(id)sender;

@end


SpecBegin(QuizViewController)

describe(@"QuizViewController", ^{
    
    __block QuizViewController *_vc;
    beforeEach(^{
        AppDelegate *delegate = [UIApplication sharedApplication].delegate;
        _vc = (QuizViewController *)[[delegate window] rootViewController];
        
        _vc.currentQuestionIndex = 0;
    });
    
    
    context(@"when setting up the view", ^{
        
        it(@"should have an outlet for the question label", ^{
            expect(_vc.questionLabel).toNot.beNil;
        });
        
        
        it(@"should wire up the question button", ^{
            UIButton *button = _vc.questionButton;
            NSArray *actions = [button actionsForTarget:_vc forControlEvent:UIControlEventTouchUpInside];
            
            expect(actions[0]).to.equal(@"showQuestion:");
        });
    });
    
    context(@"when viewing a question", ^{
        
        it(@"should set current question index", ^{
            [_vc showQuestion:nil];
            
            expect(_vc.currentQuestionIndex).to.beGreaterThan(0);
        });
        
        it(@"should display the question", ^{
            [_vc showQuestion:nil];
            
            expect(_vc.questionLabel.text).to.equal(_vc.questions[_vc.currentQuestionIndex]);
        });
    });
    
});

SpecEnd

I did a lot all in one go so I will take a second to explain what I have done. I don’t like compromising design in order to test. One such compromise is making private properties public just to test. So instead I like to use an extension class to make the properties public within the tests which is what lines 17 to 27 are doing. I then want to check: the question label is attached to the view via an outlet, the question button is wired up, when the show question button is tapped the current question index is set and finally that a question is actually displayed. So to make this pass you will need to do a number of things, so let’s get started.

Open QuizViewController.m in Xcode and add the following code:

@interface QuizViewController ()

@property (nonatomic) int currentQuestionIndex;
@property (weak, nonatomic) IBOutlet UILabel *questionLabel;
@property (weak, nonatomic) IBOutlet UIButton *questionButton;

@property (copy, nonatomic) NSArray *questions;

@end

We have created two IBOutlets to connect the question label and button to the ViewController. We have also defined an array to hold a set of questions and a property to hold the current question index. Open up QuizViewController.xib in Xcode and connect up the UILabel by right-clicking on the File’s Owner and dragging from the questionLabel in the Outlets section to the label in the view. Then connect the show question button from again right-clicking on the File’s Owner and dragging from the questionButton to the button on the view.

The next thing we will do is create the method which is called when the Show Question button is tapped. Open up QuizViewController.m and add the following code:

- (IBAction)showQuestion:(id)sender
{
}

Open up QuizViewController.xib and control drag from the Show Question button to the File’s Owner and select showQuestion:

Hook up buttonIf you run the tests now you should have one passing test: should have an outlet for the question label but the other tests should fail. Open QuizViewController.m and amend the file as follows:

//
//  QuizViewController.m
//  Quiz
//
//  Created by Dave Green on 05/05/2015.
//  Copyright (c) 2015 DeveloperDave. All rights reserved.
//

#import "QuizViewController.h"

@interface QuizViewController ()

@property (nonatomic) int currentQuestionIndex;
@property (weak, nonatomic) IBOutlet UILabel *questionLabel;
@property (weak, nonatomic) IBOutlet UIButton *questionButton;


@property (copy, nonatomic) NSArray *questions;

@end

@implementation QuizViewController

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    // Call the init method implemented by the superclass
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    
    if (self) {
        
        // Create an array filled with questions
        self.questions =    @[
                              @"From what is cognac made?",
                              @"What is 7+7?",
                              @"What is the capital of Vermont?"
                              ];
    }
    
    return self;
}

- (IBAction)showQuestion:(id)sender
{
    // Step to the next question
    self.currentQuestionIndex++;
    
    // Am I past the last question?
    if (self.currentQuestionIndex >= [self.questions count])
    {
        // Go back to the first question
        self.currentQuestionIndex = 0;
    }
    
    // Get the string at that index in the questions array
    NSString *question = self.questions[self.currentQuestionIndex];
    
    // Display the string in the question label
    self.questionLabel.text = question;
}

@end

If you run the tests again they should now pass. Before you run the Cucumber tests run the application so it has been built and then go ahead and run the Cucumber tests and you should see the following output:

$ cucumber -t @wip

Feature: Take a quiz
  As a user
  I want to take a quiz
  So I can improve my general knowledge

  @wip
  Scenario: View quiz question      # features/take_a_quiz.feature:7
    Given I am on the Quiz Screen   # features/step_definitions/quiz_steps.rb:1
    When I view a question          # features/step_definitions/quiz_steps.rb:5
    Then a question is displayed    # features/step_definitions/quiz_steps.rb:9
    And the answer is not displayed # features/take_a_quiz.feature:11

1 scenario (1 undefined)
4 steps (1 undefined, 3 passed)
0m8.436s

You can implement step definitions for undefined steps with these snippets:

Then(/^the answer is not displayed$/) do
  pending # express the regexp above with the code you wish you had
end

If everything went OK you should have one failing scenario left Then the answer is not displayed. We are going to tackle this scenario next.

Then the answer is not displayed

We don’t currently have an answer label on the view and also we don’t display the answer even if the user wanted to. So for a period this will be very easy to tackle and then when we implement some of the other features it may temporarily break and we will have to revisit.

Open QuizViewControllerSpec.m and add the following code directly under the method it(@”should have an outlet for the question label”, ^{ …

it(@"should have an outlet for the answer label", ^{
    expect(_vc.answerLabel).toNot.beNil;
});

Again within QuizViewControllerSpec.m add an IBOutlet for the question label right below the declaration of the questionButton property.

@property (weak, nonatomic) IBOutlet UILabel *answerLabel;

Open QuizViewController.xib and drag a label onto the view. Select the label in your view and set the views frame rectangle  ++5 as follows:

  • x position: 20
  • y position: 300
  • width: 280
  • height: 44

Show the Attributes Inspector ++4 and center align the label text and change the label text to ???. Within the Identity Inspector ++3 set the accessibility label to answer-label. Open QuizViewController.m and an IBOutlet for the question label right below the declaration of the questionButton property.

@property (weak, nonatomic) IBOutlet UILabel *answerLabel;

Open QuizViewController.xib and right-click on File’s Owner and connect the answerLabel outlet to the answer label you added to the view. Go ahead and run the unit tests and they should pass. We are now going to sort out the Calabash tests. Open up the quiz_steps.rb file and add the following code:

Then(/^the answer is not displayed$/) do
  query_results = query "label accessibilityLabel:'answer-label'"

  query_results.each do | result |
    expect( result['text']).to eq("???")
  end
end

Go ahead and run the cucumber tests again and everything should now pass.

$ cucumber -t @wip

Feature: Take a quiz
  As a user
  I want to take a quiz
  So I can improve my general knowledge

  @wip
  Scenario: View quiz question      # features/take_a_quiz.feature:7
    Given I am on the Quiz Screen   # features/step_definitions/quiz_steps.rb:1
    When I view a question          # features/step_definitions/quiz_steps.rb:5
    Then a question is displayed    # features/step_definitions/quiz_steps.rb:9
    And the answer is not displayed # features/step_definitions/quiz_steps.rb:17

1 scenario (1 passed)
4 steps (4 passed)
0m12.798s

Feel free to remove the @wip flag from the Cucumber tests and run all of the Cucumber tests to see how close we are to having everything completed.

$ cucumber        

Feature: Take a quiz
  As a user
  I want to take a quiz
  So I can improve my general knowledge

  Scenario: View quiz question      # features/take_a_quiz.feature:6
    Given I am on the Quiz Screen   # features/step_definitions/quiz_steps.rb:1
    When I view a question          # features/step_definitions/quiz_steps.rb:5
    Then a question is displayed    # features/step_definitions/quiz_steps.rb:9
    And the answer is not displayed # features/step_definitions/quiz_steps.rb:17

  Scenario: View next question       # features/take_a_quiz.feature:12
    Given I am on the Quiz Screen    # features/step_definitions/quiz_steps.rb:1
    And I am viewing a question      # features/take_a_quiz.feature:14
    When I view the next question    # features/take_a_quiz.feature:15
    Then a new question is displayed # features/take_a_quiz.feature:16
    And the answer is not displayed  # features/step_definitions/quiz_steps.rb:17

  Scenario: View answer                # features/take_a_quiz.feature:19
    Given I am on the Quiz Screen      # features/step_definitions/quiz_steps.rb:1
    And I am viewing a question        # features/take_a_quiz.feature:21
    When I give up and view the answer # features/take_a_quiz.feature:22
    Then the answer is displayed       # features/take_a_quiz.feature:23

  Scenario: View next question after viewing an answer # features/take_a_quiz.feature:25
    Given I am on the Quiz Screen                      # features/step_definitions/quiz_steps.rb:1
    And I am viewing a question with answer displayed  # features/take_a_quiz.feature:27
    When I view the next question                      # features/take_a_quiz.feature:28
    Then a new question is displayed                   # features/take_a_quiz.feature:29
    And the answer is not displayed                    # features/step_definitions/quiz_steps.rb:17

4 scenarios (3 undefined, 1 passed)
18 steps (2 skipped, 9 undefined, 7 passed)
0m34.863s

You can implement step definitions for undefined steps with these snippets:

Given(/^I am viewing a question$/) do
  pending # express the regexp above with the code you wish you had
end

When(/^I view the next question$/) do
  pending # express the regexp above with the code you wish you had
end

Then(/^a new question is displayed$/) do
  pending # express the regexp above with the code you wish you had
end

When(/^I give up and view the answer$/) do
  pending # express the regexp above with the code you wish you had
end

Then(/^the answer is displayed$/) do
  pending # express the regexp above with the code you wish you had
end

Given(/^I am viewing a question with answer displayed$/) do
  pending # express the regexp above with the code you wish you had
end

We now have a passing step on each scenario, so let’s move onto the next scenario.

Scenario: View next question

Add a @wip flag over the View next question scenario so we can run just the scenario in question. We currently do have a step definition for “And I am viewing a question”. So open quiz_steps.rb and add the following step definition:

Given(/^I am viewing a question$/) do
  steps %Q{
    When I view a question
  }
end

We are also going to implement the next step as well because they actually do the same thing.

When(/^I view the next question$/) do
  steps %Q{
    When I view a question
  }
end

This syntax allows you to call another step definition from a step definition, in this case the “When I view a question” step definition. Go ahead and run Cucumber again, two more steps pass and we have the next part to do “Then a new question is displayed”

 Then a new question is displayed

Within step_definitions/quiz_steps.rb we need to store the currently displayed question, amend the “When I view a question” step definition as follows:

When(/^I view a question$/) do
  macro 'I touch "show-question-button"'

  store_currently_displayed_question
end

You will also need to add the store_currently_displayed_question method. Add the following to the step_definitions/quiz_steps.rb file:

def get_currently_displayed_question
  results = query "label accessibilityLabel:'question-label'"
  results[0]['text']
end

def store_currently_displayed_question
  @last_displayed_question = @current_question
  @current_question = get_currently_displayed_question
end

Go ahead and run the Cucumber tests again and the feature you were working on should now pass.

$ cucumber -t @wip

Feature: Take a quiz
  As a user
  I want to take a quiz
  So I can improve my general knowledge

  @wip
  Scenario: View next question       # features/take_a_quiz.feature:13
    Given I am on the Quiz Screen    # features/step_definitions/quiz_steps.rb:1
    And I am viewing a question      # features/step_definitions/quiz_steps.rb:5
    When I view the next question    # features/step_definitions/quiz_steps.rb:17
    Then a new question is displayed # features/step_definitions/quiz_steps.rb:39
    And the answer is not displayed  # features/step_definitions/quiz_steps.rb:31

1 scenario (1 passed)
5 steps (5 passed)
0m10.518s

Let’s check where we are up to by running all of the Cucumber tests.

$ cucumber        

Feature: Take a quiz
  As a user
  I want to take a quiz
  So I can improve my general knowledge

  Scenario: View quiz question      # features/take_a_quiz.feature:6
    Given I am on the Quiz Screen   # features/step_definitions/quiz_steps.rb:1
    When I view a question          # features/step_definitions/quiz_steps.rb:11
    Then a question is displayed    # features/step_definitions/quiz_steps.rb:23
    And the answer is not displayed # features/step_definitions/quiz_steps.rb:31

  @wip
  Scenario: View next question       # features/take_a_quiz.feature:13
    Given I am on the Quiz Screen    # features/step_definitions/quiz_steps.rb:1
    And I am viewing a question      # features/step_definitions/quiz_steps.rb:5
    When I view the next question    # features/step_definitions/quiz_steps.rb:17
    Then a new question is displayed # features/step_definitions/quiz_steps.rb:39
    And the answer is not displayed  # features/step_definitions/quiz_steps.rb:31

  Scenario: View answer                # features/take_a_quiz.feature:20
    Given I am on the Quiz Screen      # features/step_definitions/quiz_steps.rb:1
    And I am viewing a question        # features/step_definitions/quiz_steps.rb:5
    When I give up and view the answer # features/take_a_quiz.feature:23
    Then the answer is displayed       # features/take_a_quiz.feature:24

  Scenario: View next question after viewing an answer # features/take_a_quiz.feature:26
    Given I am on the Quiz Screen                      # features/step_definitions/quiz_steps.rb:1
    And I am viewing a question with answer displayed  # features/take_a_quiz.feature:28
    When I view the next question                      # features/step_definitions/quiz_steps.rb:17
    Then a new question is displayed                   # features/step_definitions/quiz_steps.rb:39
    And the answer is not displayed                    # features/step_definitions/quiz_steps.rb:31

4 scenarios (2 undefined, 2 passed)
18 steps (3 skipped, 3 undefined, 12 passed)
0m37.991s

You can implement step definitions for undefined steps with these snippets:

When(/^I give up and view the answer$/) do
  pending # express the regexp above with the code you wish you had
end

Then(/^the answer is displayed$/) do
  pending # express the regexp above with the code you wish you had
end

Given(/^I am viewing a question with answer displayed$/) do
  pending # express the regexp above with the code you wish you had
end

We’re doing well we now have two passing scenario’s and have of the next scenario passes, so let’s finish off the View answer scenario.

Scenario: View answer

Go ahead and change the @wip flag so it is above the View answer scenario. We need to implement the step “When I give up and view the answer”. Add the following code to step_definition/quiz_steps.rb.

When(/^I give up and view the answer$/) do
  macro 'I touch "show-answer-button"'
end

We haven’t got a show answer button yet so this step isn’t going to pass yet. Open up QuizViewController.xib and drag a button onto the view. Open the Size Inspector ++5 and set the Frame Rectangle for the button as follows:

  • x position: 20
  • y position: 100
  • width: 280
  • height: 30

In the Attributes Inspector ++4 set the background colour to a grey colour so it stands out and set the text colour to white. Change the button text to “Show Answer” and in the Identity Inspector set the Accessibility Label to show-answer-button. Run the application +R and you should see your newly created button, tapping the button should do nothing at the moment.

Let’s add to the QuizViewControllerSpec.m file and replace the contents with the following:

 //
//  QuizViewControllerSpec.m
//  Quiz
//
//  Created by Dave Green on 05/05/2015.
//  Copyright (c) 2015 DeveloperDave. All rights reserved.
//


#import "Specta.h"
#import "Expecta.h"

#import "AppDelegate.h"
#import "QuizViewController.h"


@interface QuizViewController (Specs)

@property (nonatomic) int currentQuestionIndex;
@property (weak, nonatomic) IBOutlet UIButton *questionButton;
@property (weak, nonatomic) IBOutlet UILabel *questionLabel;
@property (weak, nonatomic) IBOutlet UIButton *answerButton;
@property (weak, nonatomic) IBOutlet UILabel *answerLabel;

@property (nonatomic, copy) NSArray *questions;
@property (nonatomic, copy) NSArray *answers;

- (IBAction)showQuestion:(id)sender;
- (IBAction)showAnswer:(id)sender;

@end


SpecBegin(QuizViewController)

describe(@"QuizViewController", ^{
    
    __block QuizViewController *_vc;
    beforeEach(^{
        AppDelegate *delegate = [UIApplication sharedApplication].delegate;
        _vc = (QuizViewController *)[[delegate window] rootViewController];
        
        _vc.currentQuestionIndex = 0;
    });
    
    
    context(@"when setting up the view", ^{
        
        it(@"should have an outlet for the question label", ^{
            expect(_vc.questionLabel).toNot.beNil;
        });
        
        it(@"should have an outlet for the answer label", ^{
            expect(_vc.answerLabel).toNot.beNil;
        });
        
        it(@"should wire up the question button", ^{
            UIButton *button = _vc.questionButton;
            NSArray *actions = [button actionsForTarget:_vc forControlEvent:UIControlEventTouchUpInside];
            
            expect(actions[0]).to.equal(@"showQuestion:");
        });
        
        it(@"should wire up the answer button", ^{
            UIButton *button = _vc.answerButton;
            NSArray *actions = [button actionsForTarget:_vc forControlEvent:UIControlEventTouchUpInside];
            
            expect(actions[0]).to.equal(@"showAnswer:");
        });
    });
    
    context(@"when viewing a question", ^{
        
        
        it(@"should set current question index", ^{
            [_vc showQuestion:nil];
            
            expect(_vc.currentQuestionIndex).to.beGreaterThan(0);
        });
        
        it(@"should cycle around questions", ^{
            // Work out how many questions there are
            long numberOfQuestions = [_vc.questions count];
            
            // Make sure we have questions
            expect(numberOfQuestions).to.beGreaterThan(0);
            
            // Move straight to the last question
            _vc.currentQuestionIndex = numberOfQuestions;
            
            // Now show the next question
            [_vc showQuestion:nil];
            
            // We should now be viewing the first question
            expect(_vc.currentQuestionIndex).to.equal(0);
        });
        
        it(@"should display the question", ^{
            [_vc showQuestion:nil];
            
            expect(_vc.questionLabel.text).to.equal(_vc.questions[_vc.currentQuestionIndex]);
        });
    });
    
    context(@"when viewing an answer", ^{
        
        it(@"should have an equal amount of questions and answers", ^{
            expect([_vc.questions count]).to.equal([_vc.answers count]);
        });
    });
    
});

SpecEnd

If you run the tests now they will fail because we haven’t wired up the answer button and we haven’t got an equal amount of questions and answers. Let’s open QuizViewController.m and add the necessary code to fix this. Replace the contents of the file with the following code:

//
//  QuizViewController.m
//  Quiz
//
//  Created by Dave Green on 05/05/2015.
//  Copyright (c) 2015 DeveloperDave. All rights reserved.
//

#import "QuizViewController.h"

@interface QuizViewController ()

@property (nonatomic) int currentQuestionIndex;
@property (weak, nonatomic) IBOutlet UILabel *questionLabel;
@property (weak, nonatomic) IBOutlet UIButton *questionButton;

@property (weak, nonatomic) IBOutlet UILabel *answerLabel;
@property (weak, nonatomic) IBOutlet UIButton *answerButton;

@property (copy, nonatomic) NSArray *questions;
@property (copy, nonatomic) NSArray *answers;

@end

@implementation QuizViewController

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    // Call the init method implemented by the superclass
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    
    if (self) {
        
        // Create an array filled with questions
        self.questions =    @[
                              @"From what is cognac made?",
                              @"What is 7+7?",
                              @"What is the capital of Vermont?"
                              ];
        
        self.answers = @[
                         @"Grapes",
                         @"14",
                         @"Montpelier"
                         ];
    }
    
    return self;
}

- (IBAction)showQuestion:(id)sender
{
    // Step to the next question
    self.currentQuestionIndex++;
    
    // Am I past the last question?
    if (self.currentQuestionIndex >= [self.questions count])
    {
        // Go back to the first question
        self.currentQuestionIndex = 0;
    }
    
    // Get the string at that index in the questions array
    NSString *question = self.questions[self.currentQuestionIndex];
    
    // Display the string in the question label
    self.questionLabel.text = question;
}

- (IBAction)showAnswer:(id)sender
{
    // What is the answer to the current question?
    NSString *answer = self.answers[self.currentQuestionIndex];
    
    // Display it in the answer label
    self.answerLabel.text = answer;
}

@end

Open QuizViewController.xib and control drag from the show answer button to the File’s Owner and click on showAnswer:

Run the application ⌘+Rapplication when you tap on Show Question you should see a question displayed, now when you tap on the Show Answer button you should see the answer displayed. Go ahead and run the Cucumber tests again. The step should now pass so we can go ahead and add the next failing step to step_definitions/quiz_steps.rb.

Then(/^the answer is displayed$/) do
  query_results = query "label accessibilityLabel:'answer-label'"

  query_results.each do | result |
    expect( result['text']).to_not eq("???")
  end
end

Go ahead and run the Cucumber tests again and now the scenario should all pass. If you run all the Cucumber tests again you will see we now have three passing scenario’s and only have one scenario left to finish.

cucumber
Feature: Take a quiz
  As a user
  I want to take a quiz
  So I can improve my general knowledge

  Scenario: View quiz question      # features/take_a_quiz.feature:6
    Given I am on the Quiz Screen   # features/step_definitions/quiz_steps.rb:1
    When I view a question          # features/step_definitions/quiz_steps.rb:11
    Then a question is displayed    # features/step_definitions/quiz_steps.rb:27
    And the answer is not displayed # features/step_definitions/quiz_steps.rb:35

  Scenario: View next question       # features/take_a_quiz.feature:12
    Given I am on the Quiz Screen    # features/step_definitions/quiz_steps.rb:1
    And I am viewing a question      # features/step_definitions/quiz_steps.rb:5
    When I view the next question    # features/step_definitions/quiz_steps.rb:17
    Then a new question is displayed # features/step_definitions/quiz_steps.rb:43
    And the answer is not displayed  # features/step_definitions/quiz_steps.rb:35

  @wip
  Scenario: View answer                # features/take_a_quiz.feature:20
    Given I am on the Quiz Screen      # features/step_definitions/quiz_steps.rb:1
    And I am viewing a question        # features/step_definitions/quiz_steps.rb:5
    When I give up and view the answer # features/step_definitions/quiz_steps.rb:23
    Then the answer is displayed       # features/step_definitions/quiz_steps.rb:47

  Scenario: View next question after viewing an answer # features/take_a_quiz.feature:26
    Given I am on the Quiz Screen                      # features/step_definitions/quiz_steps.rb:1
    And I am viewing a question with answer displayed  # features/take_a_quiz.feature:28
    When I view the next question                      # features/step_definitions/quiz_steps.rb:17
    Then a new question is displayed                   # features/step_definitions/quiz_steps.rb:43
    And the answer is not displayed                    # features/step_definitions/quiz_steps.rb:35

4 scenarios (1 undefined, 3 passed)
18 steps (3 skipped, 1 undefined, 14 passed)
0m38.236s

You can implement step definitions for undefined steps with these snippets:

Given(/^I am viewing a question with answer displayed$/) do
  pending # express the regexp above with the code you wish you had
end

Scenario: View next question after viewing an answer

Go ahead and move the @wip flag to the “View next question after viewing an answer” scenario. Open step_definitions/quiz_steps.rb and add the following step definition:

Given(/^I am viewing a question with answer displayed$/) do
  steps %Q{
    Given I am viewing a question
    When I give up and view the answer
  }
end

Now if you run the Cucumber tests everything should pass!

$ cucumber

Feature: Take a quiz
  As a user
  I want to take a quiz
  So I can improve my general knowledge

  Scenario: View quiz question      # features/take_a_quiz.feature:6
    Given I am on the Quiz Screen   # features/step_definitions/quiz_steps.rb:8
    When I view a question          # features/step_definitions/quiz_steps.rb:18
    Then a question is displayed    # features/step_definitions/quiz_steps.rb:34
    And the answer is not displayed # features/step_definitions/quiz_steps.rb:42

  Scenario: View next question       # features/take_a_quiz.feature:12
    Given I am on the Quiz Screen    # features/step_definitions/quiz_steps.rb:8
    And I am viewing a question      # features/step_definitions/quiz_steps.rb:12
    When I view the next question    # features/step_definitions/quiz_steps.rb:24
    Then a new question is displayed # features/step_definitions/quiz_steps.rb:50
    And the answer is not displayed  # features/step_definitions/quiz_steps.rb:42

  Scenario: View answer                # features/take_a_quiz.feature:19
    Given I am on the Quiz Screen      # features/step_definitions/quiz_steps.rb:8
    And I am viewing a question        # features/step_definitions/quiz_steps.rb:12
    When I give up and view the answer # features/step_definitions/quiz_steps.rb:30
    Then the answer is displayed       # features/step_definitions/quiz_steps.rb:54

  @wip
  Scenario: View next question after viewing an answer # features/take_a_quiz.feature:26
    Given I am on the Quiz Screen                      # features/step_definitions/quiz_steps.rb:8
    And I am viewing a question with answer displayed  # features/step_definitions/quiz_steps.rb:1
    When I view the next question                      # features/step_definitions/quiz_steps.rb:24
    Then a new question is displayed                   # features/step_definitions/quiz_steps.rb:50
    And the answer is not displayed                    # features/step_definitions/quiz_steps.rb:42

4 scenarios (4 passed)
18 steps (18 passed)
0m40.849s

Some Common Pitfalls

Congratulations on getting this far, if you decided to adopt a BDD workflow for your development then things can get more complicated than this. Some of the common pitfalls that I have encountered so far are:

  • Forgetting to add new files to the -cal target. When you forget to do this the tests fail so it is easy to spot and you need to add the new file to the -cal target, easy enough but can be annoying.
  • When you start to get data from services you will need to add dependency injection or start mocking services.
  • Things can get complicated if you have cross-cutting concerns such as analytics and crash reporting because you may need to mock these services out as well.

In future blog posts I am going to look at mocking services and dealing with cross-cutting concerns within your BDD workflow.

1 reply

Trackbacks & Pingbacks

  1. […] written before on the subject of Behaviour-Driven Development, here and here. I have found it invaluable in answering the question “what should I test?” Focussing […]

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *