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

  1. Intro & User Stories
  2. Console-Based Application
  3. Timers in a Console-Based Application

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.


Brought to you by CEUHelper - Simple, inexpensive, and paperless CEU management for conferences.





Share this story