"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.
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.
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| @computed_answer = @array.send(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| expect(@computed_answer).to be_within(0.001).of(expected_answer.to_f) 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?
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. {Cucumber Test Results}[link:cuke_results.html]
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.
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.
Copyright © 2005-2023 Amp Books LLC All Rights Reserved