I have not spent a lot of time writing code in Ruby recently, but this weekend had the opportunity to develop a very simple trivia application in vanilla Ruby: two classes, a few dependencies, not much else. While this was easy enough, I wanted to add some tests to one of my classes, and I need a refresher. This post details my reintroduction to RSpec.
As background, the application took a JSON file that contained many questions, each an object with three keys: a question (value: a string), an answer (value: a string), and incorrect (value: an array of strings). I wrote a Clue class (not the most descriptive name given it includes answers, but so it goes sometimes) with one function to parse the JSON file. This looked like:
class Clue
attr_accessor :question, :answer, :wrong_answers, :all_answers
@@all = [] #an array of all the clues
def initialize()
@@all << self
end
def self.all
@@all
end
def self.get_trivia
json = JSON.parse(File.read("file_name.json"))
json.each do |res|
clue = Clue.new
clue.question = res['question']
clue.answer = res['correct']
clue.wrong_answers = res['incorrect']
clue.all_answers = [res['correct']].concat(res['incorrect'])
end
end
end
Having once upon a not-so-distant time spent a lot of time interacting with RSpect unit tests, I decided to use RSpec rather than Ruby'y standard library test-unit
.
To get started with RSpec, you need to ensure that you have included the gem 'rspec'
in your Gemfile and have that installed correctly, then in your command line from the root direct run rspec --init
. This will create a directory 'spec' with the file 'spec-helper.br' as well as a root file '.rspec'.
I next created a file under spec called 'clue_test.rb' which is where all of the work happens. It's easy to forget, but you need to remember to import the class you are working with in the test file, in this case: require 'clue'
. The next step was to test the different attributes of the Clue class. With testing, it is best to make the tests small and simple (an easy thing to do in a simple application).
To start, I wanted to create a test Clue
object that would be the basis for the tests. RSpec relies on the verb describe
for a lot of its tests, and as my tests were describing the clue class, that's what we began with: describe Clue do
. Before any tests run, a new clue was created, fed with data from a Hash. There could be other ways of doing this, but I liked simulating the logic of the JSON parsing function.
require 'clue'
describe Clue do
before do
new_clue = Hash["question" => "How many folds does a chef's hat have?", "answer" => 100, "wrong_answers" => [90, 115, 50]]
@first_clue = Clue.new
@first_clue.question = new_clue['question']
@first_clue.answer = new_clue['answer']
@first_clue.wrong_answers = new_clue['wrong_answers']
@first_clue.all_answers = [new_clue['answer']].concat(new_clue['wrong_answers'])
end
end
The next step was to write a test. Go one by one. I wanted to start with some very simple. RSpec works by outlining a condition and then describing how the condition should behave. Tests generally look like something like:
describe "thing" do
it "behaves like this" do
expect(variable).to eq(other thing)
end
end
For the first test, I wanted to simply check if the wrong_answers
attribute of the newly instantiated Clue was an array. This looked:
describe "wrong answers" do
it "includes an array of wrong answers" do
expect(@first_clue.wrong_answers.class).to eq(Array)
end
end
In the initial writing of my tests, I had actually passed the wrong_answers to the Clue as a hash. It's a small but significant error, and one that really helps you to get to know your code much better, regardless of how simple you think it.
There are a whole host of things you can beyond checking equivalence in RSpec. One simple test I wrote checked if the .all_answers
attribute include something from the .wrong_answers
array:
describe "all answers" do
it "includes wrong answers" do
expect(@first_clue.all_answers).to include(@first_clue.wrong_answers[0])
end
end
In the end, I wrote a very simple test suite, but one that got me much closer to my code. I encourage you to check out the documentation and always test your code.
Top comments (0)