Sometimes a command-line application needs to ask the user a question. Aruba supports this, allowing you to write scenarios that include a user responding to requests for information from the command-line application. We’ve been getting feedback from user testing of our calculator that a significant number of our users want to be able to perform additions without saving them to a file first. Let’s write another scenario to describe that use case:
| Scenario: Add two numbers interactively |
| When the calculator is run with no input |
| And I enter the calculation "2+2" |
| Then the output should be "4" |
Run cucumber to generate the new step definition snippets, and then paste them into features/step_definitions/calculator_steps.rb:
| When(/^the calculator is run with no input$/) do |
| pending # Write code here that turns the phrase above into concrete actions |
| end |
| |
| When(/^I enter the calculation "([^"]*)"$/) do |arg1| |
| pending # Write code here that turns the phrase above into concrete actions |
| end |
We’ll implement the first step definition by delegating to a variation of the Aruba step definition we used earlier. This time, we’ll tell Aruba we’re going to interact with the child process:
| When(/^the calculator is run with no input$/) do |
| steps %{ |
| When I run `calculator` interactively |
| } |
| end |
| |
| When(/^I enter the calculation "([^"]*)"$/) do |calculation| |
| steps %{ |
| When I type "#{calculation}" |
| } |
| end |
We’ve used two new Aruba step definitions here. You can probably guess how they work, but let’s explain them:
|
Executes the command between the backticks in a child process and connects to the STDIN, STDOUT, and STDERR streams, ready for subsequent steps to interact with the child process. | ||
|
Sends the given text to the running process’ STDIN stream. |
Run cucumber, and you should have a failing test. We can see there’s an error here, because our calculator isn’t executing successfully, but we aren’t able to see what the problem is. We can use @announce-output again to see the output. This time we’ll just add it to our new scenario.
| @announce-output |
| Scenario: Add two numbers interactively |
| When the calculator is run with no input |
| And I enter the calculation "2+2" |
| Then the output should be "4" |
Run cucumber, and you should see the error, and the output from the calculator command:
| Feature: Adding |
| |
| Scenario: Add two numbers |
| Given the input "2+2" |
| When the calculator is run |
| Then the output should be "4" |
| |
| @announce-output |
| Scenario: Add two numbers interactively |
| When the calculator is run with no input |
| And I enter the calculation "2+2" |
| <<-STDOUT |
| |
| STDOUT |
| <<-STDERR |
| ~/command_line_applications/11/bin/calculator:2:in `read': |
| no implicit conversion of nil into String (TypeError) |
| from ~/command_line_applications/11/bin/calculator:2:in `<main>' |
| |
| STDERR |
| <<-STDOUT |
| |
| STDOUT |
| <<-STDERR |
| ~/command_line_applications/11/bin/calculator:2:in `read': |
| no implicit conversion of nil into String (TypeError) |
| from ~/command_line_applications/11/bin/calculator:2:in `<main>' |
| |
| STDERR |
| Then the output should be "4" |
| expected "calculator" to be successfully executed |
| (RSpec::Expectations::ExpectationNotMetError) |
| ./features/step_definitions/calculator_steps.rb:18 |
| features/adding.feature:13 |
| <<-STDOUT |
| |
| STDOUT |
| <<-STDERR |
| ~/command_line_applications/11/bin/calculator:2:in `read': |
| no implicit conversion of nil into String (TypeError) |
| from ~/command_line_applications/11/bin/calculator:2:in `<main>' |
| |
| STDERR |
| <<-STDOUT |
| |
| STDOUT |
| <<-STDERR |
| ~/command_line_applications/11/bin/calculator:2:in `read': |
| no implicit conversion of nil into String (TypeError) |
| from ~/command_line_applications/11/bin/calculator:2:in `<main>' |
| |
| STDERR |
| |
| Failing Scenarios: |
| cucumber features/adding.feature:10 |
| |
| 2 scenarios (1 failed, 1 passed) |
| 6 steps (1 failed, 5 passed) |
| 0m0.890s |
We’ve had an error from line 2 of our calculator program, where we try to read the contents of the input file passed as a command-line argument. That’s because we haven’t changed our application to wait for user input yet, so it’s still expecting to be told what file to read. We need to change our calculator program to ask for input if no file is specified:
| #!/usr/bin/env ruby |
| input = if ARGV.any? |
| File.read(ARGV[0]) |
| else |
| puts "Please enter the calculation you'd like me to perform" |
| gets |
| end |
| |
| numbers_to_add = input.split('+').map { |n| n.to_i } |
| |
| total = 0 |
| numbers_to_add.each do |number| |
| total += number |
| end |
| |
| print(total) |
Now if there are no command-line arguments, our calculator will ask the user to enter the calculation and then sit and wait for them to type something. Remove the @announce-output tag from the scenario, and let’s see how this plays out when we run our scenario:
| Feature: Adding |
| |
| Scenario: Add two numbers |
| Given the input "2+2" |
| When the calculator is run |
| Then the output should be "4" |
| |
| Scenario: Add two numbers interactively |
| When the calculator is run with no input |
| And I enter the calculation "2+2" |
| Then the output should be "4" |
| |
| 2 scenarios (2 passed) |
| 6 steps (6 passed) |
| 0m0.654s |
Excellent! We’ve modified the calculator, and not only is our new scenario passing, but so is the first one, which means we haven’t broken the existing behavior. Let’s make one last change and refactor our step definitions to make them even cleaner. Instead of delegating to Aruba’s step definitions, let’s use the Aruba API directly.
18.119.103.96