Creating a Simple Data Collection App: Part 2
Unhappy with the current data collection apps out there? Then create your own!
Ruby Code Review: Data Collection Program
Other parts in this series
Check back later for the other parts of this tutorial
This is Part 2 of a multi-part code sample and review for creating a simple data collection system using Ruby. Future parts will be released soon!
Part 2 Overview
Check out the Ruby Data Collector on Github
Or check out the Relevant Commit for this step in the review.
Part 1 of this series involved creating some user stories that defined some of the key aspects of the Data Collection System I want to build. In Part 2, I have created tests that define the API for some of the core classes and build out the application for use in the console.
Converting User Stories to Tests
Unfortunately, we are not in the browser yet, so acceptance tests with a testing framework like Capybara isn’t available yet. Capybara would allow me to write tests that would match the users stories more closely. For example:
feature "start the session" do
scenario "user clicks the 'start session' button on the home page" do
visit "/"
click_button("Start Session")
expect(current_path).to eq("/session")
expect(page).to have_content("Timer: ")
expect(page).to have_content("Track Behavior A")
end
end
However, we can still convert our user stories in to tests. Let’s take on User Story as an example.
Choosing a Session Duration
- As a behavior analyst
- I want to set the duration for the session
- So that the session will end at the correct time
So this user story suggests that we have to have a thing called “Session” and that it will have an attribute called “session_duration”. The session duration should probably be set when the Session is instantiated because a Session without a duration would not be useful or make sense.
Here is a test for that:
require_relative "../spec_helper"
describe Session do
let(:session_duration) { 5 }
let(:session) { Session.new(session_duration) }
describe ".new" do
it "creates a new session" do
expect(session).to be_a(Session)
end
it "accepts a session duration in minutes on creation" do
expect { Session.new(5) }.not_to raise_error
end
end
describe "#duration_in_min" do
it "returns the duration of the session" do
expect(session.duration_in_min).to eq(5)
end
end
end
This test makes sure we can pass in the duration as a parameter when a new session is created. It also ensures that when we ask for the session duration, it is returned appropriately.
Challenges When Testing for a Console App
One challenge I encountered, particularly when dealing with a console app, is handling the sequence of gets
that happen. For example, here is the sequence of questions the user is asked to get the session setup:
- How many minutes is this session?
- 5
- What is the name of the behavior?
- Aggression
- What key on the keyboard will represent this behavior?
- a
- Would you like to add another behavior? (Y/n)
- n
Fortunately, RSpec allows us to stub our the gets
method so we can “answer” these questions in our tests.
it "runs the session" do
allow(data_collector).to receive(:gets)
.and_return("5")
expect { data_collector.get_session_info }.not_to raise_error
end
This works great! But only if we have a single gets
request. I was stuck on how to create more of an acceptance test feel for a test that could walk through the whole application. I eventually stumbled upon a doc that indicated multiple parameters could be passed into the and_return
. On every subsequent get
request it will return the next value passed in.
If we wanted to answer the questions above in order in a test, we could use the following code.
it "runs the session" do
allow(data_collector).to receive(:gets)
.and_return("5", "Aggression", "a", "n")
expect { data_collector.get_session_info }.not_to raise_error
end
The first time gets
is called, it will return the value “5”, the second time, “Aggression”, and so on. This allows the test to walk through the app as if a user is entering values when prompted.
In the next part of this review, I will discuss some of the challenges of adding a timer.