Exercise 3: Acceptance Testing

by Richard Kuehnel

"It is sometimes said that unit tests ensure you build the thing right, while acceptance tests ensure you build the right thing." - Matt Wynne and Aslak Hellesoy, The Cucumber Book

This exercise covers the following topics.

1. the purpose of acceptance testing
2. testing new features using Cucumber
3. writing Cucumber step definitions

Acceptance Tests

Acceptance tests are different from unit tests, which help developers check their software designs. Acceptance testing is used to ensure that project requirements are met. To make certain that a user story has been successfully implemented, developers and stakeholders collaborate on scenarios. As a collaboration tool, the RubyGem Cucumber enables anyone on the team, both developers and customers, to read and write these scenarios. The readability of Cucumber draws stakeholders into the software development process, to make sure that everyone on the team understands the requirements of the project.

Cycle One

In statistics, standard deviation is the square root of the variance. The variance of `[1,2,3,4,5]`, for example, is equal to 2. Thus the standard deviation is the square root of 2, which is equal to approximately 1.414.

OK, that is enough mathematics - let's get back to Ruby! If you were to write an RSpec test for standard deviation, it would look something like this:

```describe 'Statowl statistics' do
...

describe 'the array [1,2,3,4,5]' do
input = [1,2,3,4,5]
it 'returns a standard deviation of about 1.414' do
expect(input.standard_deviation).to be_within(0.001).of(1.414)
end
end

...
end
```

RSpec is quite readable compared to the unit testing package contained in core Ruby. Nevertheless, the lavish adornment of software syntax will not thrill business stakeholders. It is, after all, valid Ruby code. By contrast, here is a Cucumber acceptance test for the same feature:

```Feature: Get statistics from numbers
If I have some numbers
I want to easily compute statistics on them

Scenario: Get the standard deviation
Given I have an array containing "1,2,3,4,5"
When I compute its "standard_deviation"
Then I get approximately "1.414" as an answer.
```

Clearly Cucumber is more accessible to stakeholders. In fact, with a little training they could write features themselvers. The key formatting constraints are proper indentation and the placement of double quotes, both of which are easy to learn.

Normally if the rest of your statistical methods were being tested via RSpec, then you would not suddenly shift to Cucumber. Just for practice, however, insert the Cucumber "code" above into a new file `features/statistics.feature`. Then execute `rake features`.

Step definitions translate the steps of a Cucumber test into executable Ruby code. Cucumber, in this case, tells you that three step definitions are undefined. It also lends you a hand, however, by giving you three snippets of code that you can copy to a step definition file.

Copy the snippets to `features/step_definitions/statistics_steps.rb`. Then run `rake features` again to see that the steps are now defined but that all are pending full implementation. When Cucumber reaches a `pending` call in a step definition, it skips the rest of the scenario. Thus Cucumber indicates that you have 1 pending and 2 skipped steps.

Modify the first step as follows to create an array of numbers from the input string.

```Given(/^I have an array containing "(.*?)"\$/) do |number_string|
@array = number_string.split(',').map { |number| number.to_f }
end
...
```

Notice that you have changed the non-descriptive variable name `arg1` with a more meaningful name. Ruby should be written for clarity and readability.

Check that the first step passes. Then implement the second step definition:

```...
When(/^I compute its "(.*?)"\$/) do |method_name|
end
...
```

Notice that you are using the `send` method, which is convenient for converting strings into method invocations. Practically speaking, these two statements are equivalent:

```[1,2,3,4].mean
[1,2,3,4].send("mean")
```

Check that the step fails because the standard Ruby `Array` class has no `standard_deviation` method. Add the following to the top of `features/step_definitions/statistics_steps.rb` to link these steps to your production code.

```require_relative '../../lib/statowl/extensions'

...
```

Still red? Add an empty method to `lib/statowl/extensions.rb`.

```...
def standard_deviation
end
...
```

Does the step pass? This means you must stop writing production code. You cannot fully implement `standard_deviation` at the moment because you do not have a failing test. Instead, implement the third step definition:

```...
Then(/^I get approximately "(.*?)" as an answer\.\$/) do |expected_answer|
end
```

This final step should fail. Make it pass by adding the least amount of code possible to `lib/statowl/extensions.rb`:

```...
def standard_deviation
1.414
end
...
```

Refactor?

Cycle Two

The standard deviation of `[6,7,8,9]` is approximately `1.118`. Add a new Cucumber scenario to `features/statistics.feature` that applies triangulation to render your trivial implementation of the `standard_deviation` method invalid:

```...

Scenario: Get the standard deviation
Given I have an array containing "1,2,3,4,5"
When I compute its "standard_deviation"
Then I get approximately "1.414" as an answer.

Scenario: Get the standard deviation
Given I have an array containing "6,7,8,9"
When I compute its "standard_deviation"
Then I get approximately "1.118" as an answer.
```

Then make the scenario pass by modifying `lib/statowl/extensions.rb` to take the square root of the variance instead of returning a hard-coded answer.

Refactor?

The test results are in `doc/cuke_results.html` because that is the filename you specified in `Rakefile`. Create a convenient link to the test results page by adding the following to `README.rdoc`.

```== Testing

This project uses RSpec and Cucumber for Behavior Driven Development.

```

Execute the following commands.

```rake yard
bin/webrick
```

Once the WEBrick server has started up, refresh or navigate to `http://localhost:8080/`. Click on "Class List" at the top of the page. Not much there, eh? Click on "Statowl" to observe that you have VERSION defined and namespaced under the Statowl module that GLI created.

Click on the "Cucumber Test Results" link on the main page that you created by editing `README.rdoc`. All green - you have zero failures!

Your statistics API is now complete. It gives arrays of numbers the ability to compute their mean, median, variance, and standard deviation and it enables arrays to be compared based on their mean. The API is lean, mean, and accompanied by fully automated tests. You wrote acceptance tests for standard deviation and unit tests for everything else.

Add the fully tested `lib/statowl/extensions.rb` to your running list of production code files in `lib/statowl.rb`:

```...
require 'statowl/extensions'
```

Your next assignment will be to transform the API into an awesome command line app.

Exercise Summary

In this exercise you learned how to implement acceptance tests using Cucumber. Unit tests are oriented toward software developers who want to validate their designs. Acceptance tests can accomplish the same result, but they are also designed to facilitate communication and collaboration between developers and stakeholders.

"Unit tests ensure you build the thing right, while acceptance tests ensure you build the right thing."

In the next exercise you will learn how to apply Behavior Driven Development to command line applications.