Test-driven calculator app using Ruby and RSpec

23 Sep 2019

I have been trying to learn Test-driven development alongside Kotlin, object-oriented programming and design patterns. Got to the point where I was burned out because all of these things are difficult and require a huge amount of mental energy.

So I decided to look for a few gentler TDD courses. By gentler, I mean in a sanguage I'm more comfortable with, and using examples that are easier for me to follow along with. A good candidate for this has been Fundamentals of TDD which I am going to work through in this entry.

Test-Driven Development (TDD) introduction

Building out a calculator

Write the interface you want to interact with

The instructors started out by writing out the interface they want to interact with. This is an important theme in TDD. The interface is what you use regularly so it's important that it's easy to understand and interact with. By writing tests first, you have more control over how the interface looks. So this is a technique worth highlighting. Before you write every test, write the interface you want and comment it out to guide the way you then write your test.


require 'rspec/autorun'

describe Calculator do
  describe "#add" do
    it "returns the sum of its two arguments" do
      calc = Calculator.new

      expect(calc.add(5, 10)).to eq(15)
    end
  end
end

The following is a list of steps that the instructors took to write their first test in RSpec, Ruby. The code for this first test is what you see above. Some knowledge of RSpec and how the tests are structured is assumed.

I found it interesting that they created the calculator before wrinting the test assertion which called a method on it. So they are writing the test based on how they would use it in a real application. They are programming to the interface they want to use.

Steps to passing first test.


class Calculator
end


class Calculator
  def add
  end
end


class Calculator
  def add(a,b)
  end
end


class Calculator
  def add(a,b)
    15
  end
end

While the code we have written above is not very general, we have used a hard-coded value, it pushes us towards writing a new test that gorces us to write more general code.

Tests that push us from specific to general.

The next test we write pushes us to write more specific code. To write a new test, we add another 'it' block which describes the new behaviour we want to test. In our next it block, we are testing that the calculator returns the sum of two different arguments.


it "returns the sum of two different arguments" do
  calc = Calculator.new

  expect(calc.add(1,2).to eql(3)
end


class Calculator
  def add(a, b)
    if a == 5
      15
    else
      3
    end
  end
end


Our next test will be similar to our last one, this time our test will say "returns the sum of another two different arguments"

  • To make this test, we could do the same thing as before and write another if statement to account for the third case. In fact, we do do this to make the next error message pass.
  • Now that our test is passing again it's time to take this opportunity to refactor. Refactoring is where you change the structure of your code without changing it's behaviour. In this case, we can now refactor to remove the duplication we have created in passing our test using if statements.
  • We are going to return a + b instead of using all of those if statements. We could have done this after writing the previous test, but instead decided to wait until the rule of three had been observed. The rule of three is where you remove duplication after something has been duplicated not once, not twice but three times. This approach works well for more complicated programs, because often we can cause design issues by removing duplication too early by making our code more complex because of anticipated, not observed changes.