Draft-to-Refactored Module: The First Test


Let’s bootstrap our test and class files, write our first test, and make it pass.

I’ll create a new directory to hold my application and call it, “resistor”.

I’ll launch TMUX inside of the directory.

Now I’ll create the Rakefile I want to use to execute the tests:

# Rakefile

1 require 'rake/testtask'
3 Rake::TestTask.new(:test) do |t|
4   t.test_files = FileList["./*_spec.rb"]
5 end
7 task default: :test

By using this rake file I can execute all files in the directory with names ending in _spec.rb by simply running the command rake. That’s the command I have wired up to <F4> as i mentioned in the lesson titled “My TMUX & VIM Setup” from Module 1.

Notice that I’m using _spec here rather than _test like we did in the previous exercises. That’s because when I’m doing TDD I prefer to use spec-style syntax. I find that it helps me drive out the behavior more intuitively.

Let’s go ahead and split our window into a vim pane and a test running pane, and create the spec file - resistor_spec.rb

# ./resistor_spec.rb#L1-L4

1 require 'minitest/autorun'
2 require_relative './resistor'

Now, I’m still using Minitest and not Rspec. Minitest has a spec syntax that’s pretty close to Rspec, but you get Minitest when you install Ruby. It also feels a bit faster to me, but I haven’t done any benchmarking. So take that with a grain of salt.

I also require_relative './resistor' which I’ll create in a minute.

With spec syntax, rather than defining a class for our test, we describe the class under test, so describe Resistor. The describe method takes a block, so we need a do and an end.

# ./resistor_spec.rb#L4-L6

4 describe Resistor do
6 end

I’ll start with the #tolerance method since it’s one of the simplest and doesn’t affect much else.

In the class describe block, I’ll describe '#tolerance'. Again, I’m using the ‘#’ to indicate that I’m referring to an instance method.

Before worrying about test setup, I’ll think about the behavior I expect from this method.

If I assume that I have as a subject a Resistor initialized with the four colors from our example - green, blue, yellow, gold - then the tolerance method will return the value, ‘±5%’.

I’ll write the code I wish I had and then let the failures guide me in setting up the test and implementing the method.

With spec syntax, you don’t write test methods, you write ‘it’ blocks to specify behavior. So inside of our tolerance method block I’ll write, it ‘returns the correct value’ do end.

And instead of using assert, I’ll use the “expect” syntax. To test for equality with an expected value, expect(subject.tolerance).must_equal '±5%'. By the way, if you are following along in a mac, you type the ‘±’ sign by holding option and shift and typing ‘=‘.

If you are used to RSpec, notice that you can’t use to_equal here, but need to use must_equal instead. I prefer to_equal, but it’s not available out of the box with Minitest.

# ./resistor_spec.rb#L4-L10

   4 describe Resistor do
   5   describe '#tolerance' do
   6     describe 'when given 4 colors' do
   7       it 'returns the correct value' do
   8         expect(subject.tolerance).must_equal '±5%'
   9       end
  10     end

I’ll run the test and let the failures guide me.

Now, when I’m doing this I like to predict what is going to happen before I run the test to make sure I understand what’s going on. If I’m surprised by the outcome, it’s an extra nudge to make sure I know what happened.

This test run is going to fail because I haven’t created the ‘resistor.rb’ file yet.

Yep. So let’s create the file and run it again. I’ll also swap the panes so my class under test is on the left and my test on the right.

Now it will fail because we haven’t created the Resistor class.

So let’s define the class.

Now it will fail because we haven’t defined a tolerance method on our Resistor class.

Oh! No. This is another difference between RSpec and Minitest. Minitest does not automatically populate “subject” with an instance of the class under test. We have to do that ourselves.

We can use the “let” syntax to define objects and behavior for our test to use.

Since all of our tests will need access to an instance of our subject, I’ll put this in the outermost block.

# ./resistor_spec.rb#L4-L12

 4 describe Resistor do
 5   let(:subject) { Resistor.new }
 7   describe '#tolerance' do
 8     it 'returns the correct value' do
 9       expect(subject.tolerance).must_equal '±5%'
10     end
11   end
12 end

We’ll need to initialize this with colors in a sec, but I’m going to be fairly rigid in doing the simplest possible thing to make a test pass and then moving on, letting my tests guide me as I go.

So now our test will fail because we don’t have a “tolerance” instance method.

I’ll define it. Next, the test will fail because we are returning “nil” rather than the expected value.

So let’s hard-code the return value and the test will finally pass.

Finally we are green!

Now, I know that the tolerance will not always be “±5%”.

So let’s set up a couple of cases to force us to drive out the actual behavior we want.