DEV Community

Cover image for Basic Ruby Calculator with RSpec
Meks (they/them)
Meks (they/them)

Posted on

Basic Ruby Calculator with RSpec

Testing is an important part of Software Development, it can be slow to start especially as you're learning it, but it is such an important savior in the long run. As projects get bigger you can run tests to make sure new features don't break old ones and changes don't have unexpected consequences.

To get started I decided to learn and get some practice using RSpec, which is a unit test framework for the Ruby programming language. RSpec is a Behavior-driven development tool, meaning that tests written in RSpec focus on the "behavior" of an application being tested. RSpec doesn't put emphasis on, how the application works but instead on how it behaves, in other words, what the application actually does.

Here is my first try at building a simple ruby calculator using test-driven development(TDD)! TDD is a software development process relying on software requirements being converted to test cases before software is fully developed, and tracking all software development by repeatedly testing the software against all test cases. It is sometimes referred to as red-green testing because you write tests for what you want the code to do, then you run the tests to see them fail (red). Then you write the code to make the tests pass(green).

To start, make sure you have ruby and RSpec installed. You can check that is the case by running

$ ruby -v
ruby 2.6.1p33 (2019-01-30 revision 66950)
Enter fullscreen mode Exit fullscreen mode

and

$ rspec -v
RSpec 3.10
  - rspec-core 3.10.1
  - rspec-expectations 3.10.1
  - rspec-mocks 3.10.2
  - rspec-support 3.10.2
Enter fullscreen mode Exit fullscreen mode

If you are missing either of these you can check out the documentation for Ruby and to add RSpec you can run

$ gem install rspec
Enter fullscreen mode Exit fullscreen mode

A gem is a Ruby library that you can use in your own code and you can install these using the gem command.

Next, let's make a folder in a location of your choice called calculator.

$ mkdir calculator
Enter fullscreen mode Exit fullscreen mode

Let's cd into the folder and open it up with your code editor.

$ cd calculator
$ code .
Enter fullscreen mode Exit fullscreen mode

Next we are going to need a Gemfile that knows which version of RSpec to run.

$ touch Gemfile
Enter fullscreen mode Exit fullscreen mode

In that gem file add

source "https://rubygems.org"

so it knows where to get its gems from. Then in your terminal

$ bundle add rspec
Enter fullscreen mode Exit fullscreen mode

and your gemfile will be updated to have the latest version of rspec available to your project.

Alt Text

Now we can make folders called lib to hold the Calculator class and spec to hold the tests which is a common practice.

$ mkdir lib
$ mkdir spec
Enter fullscreen mode Exit fullscreen mode

Now for the fun part! Let's make a file called calculator_spec.rb, it's common practice to have your test file have the name of the file it's testing plus _spec. And another file called calculator.rb in your lib folder.

First we are going to require the file that we are testing at the top of the spec file:

require './lib/calculator.rb'
Enter fullscreen mode Exit fullscreen mode

Now let's write our first test! We want our Calculator to be able to add two numbers and return the result.

describe Calculator do 
  context "Given two numbers" do 
    it "adds the numbers using the add method" do
      calc = Calculator.new
      sum = calc.add(2,3)
      expect(sum).to eql(5)
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Woah, Nelly! What's all that! Ok. So. The word describe is an RSpec keyword. It is used to define an “Example Group”, which is a collection of tests. The describe keyword can take a class name and/or string argument, in our case we used the Calculator class name (which doesn't exist yet). You also need to pass a block argument to describe, this will contain the individual tests, or as they are known in RSpec, the “Examples”. The block is just a Ruby block designated by the Ruby do/end keywords.

So our describe Calculator block will wrap the individual tests. Inside the describe block is context, another RSpec keyword. It is very similar to the describe block, and is not mandatory, but it can help add more details to what the tests are doing. In this case, we are just testing for handling two numbers entered into the function.

The word it is another RSpec keyword used to define an “Example”, ie a test case. it is passed a string and a block argument designated with do/end (like describe and context, it can accept a class, but it would be unusual to do so). The string of it describes the expected outcome for the test. The string makes it clear what should happen when we call add on an instance of the Calculator class. As part of the RSpec philosophy, an Example is not just a test, it’s also a specification (a spec). So the Example both documents and tests the expected behavior of your Ruby code.

We then make an instance of the Calculator class so that we can call the add method on it to test if it does actually add together properly. Let's set a variable of sum equal to the result of passing in the numbers 2 and 3 into the add method.

And now, the actual test! The expect keyword is used to define an “Expectation” in RSpec. This is where we verify, that a specific expected condition has been met. The idea is that expect statements should read like normal English. You can say this aloud as “Expect the sum to equal 5”. The idea is that it's descriptive and easy to read.

The to keyword is used as part of expect statements. to is used with a dot, expect(sum).to, because it actually just a regular Ruby method. In fact, all of the RSpec keywords are really just Ruby methods.

And finally, we have the eql keyword which is a particular kind of RSpec keyword called a Matcher. Matchers are used for specifying what type of condition you are testing to be true (or false). Here we are expecting the add method to return an integer that matches 5.

All right! Let's run our test with the command rspec and the file designated in the spec folder!

$ rspec spec/calculator_spec.rb   
Enter fullscreen mode Exit fullscreen mode

Alt Text

Our error tells us that the constant Calculator is uninitalised, which makes sense because we haven't built our Calculator class yet! So in the calculator.rb file, let's make our class:

class Calculator 
end
Enter fullscreen mode Exit fullscreen mode

and run the tests again.
Alt Text

Woohoo! 1 failure! This is good. We are in the red part of red/green testing. There's no method add. So let's fix that.

def add
end
Enter fullscreen mode Exit fullscreen mode

Alt Text

Now our test is telling us that the add method received 2 arguments, but was expecting 0. This is because in the test we gave it two, but in the actual code it doesn't have any arguments passed into the method So let's pass it two arguments.

 def add(x,y)
 end
Enter fullscreen mode Exit fullscreen mode

Alt Text

Now the test was expecting 5 to be returned, but it failed because we didn't return anything. Let's fix that.

  def add(x,y)
    return 5
  end
Enter fullscreen mode Exit fullscreen mode

Alt Text

Woo! It passed! But...wait...that's silly, isn't it? Well, yes, you're right. If you write your tests and make them pass with the minimum amount of work, and it doesn't actually behave how you want, then you need to upgrade your tests to be a bit more robust. Let's add another test!

    it "can add two different numbers" do 
      calc = Calculator.new
      sum = calc.add(8,23) 
      expect(sum).to eql(31)
    end
Enter fullscreen mode Exit fullscreen mode

Alt Text
Ok, so we're passing the first test, but not the second. Let's fix is so we pass both!

  def add(x,y)
    return x + y
  end
Enter fullscreen mode Exit fullscreen mode

Alt Text
Yes! Two passing tests!

Did anyone else notice that we initialized a new instance of the calculator class inside both it blocks? We can refactor this to using the RSpec before hook which takes in a symbol indicating the scope and a block of code to execute. The before(:each) block will run before each example.

describe Calculator do 

  before(:each) do 
    @calc = Calculator.new 
  end

  context "Given two numbers" do 
    it "can add the numbers using the add method" do
      sum = @calc.add(2,3)
      expect(sum).to eql(5)
    end

    it "can add two different numbers" do 
      sum = @calc.add(8,23) 
      expect(sum).to eql(31)
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Run this code again and both tests are still passing!

If you want to try adding different numbers with your new add method you can hop into an irb session in your terminal and load the file that has your calculator class, instantiate a new instance of that class storing it in a variable, then call the add method on it with the two numbers you want to add as arguments. Type exit to get out of your irb!

Alt Text

Go ahead and try following the red/green TDD process for subtract, multiply and divide. Don't forget! You may want to test for a user trying to divide by zero and display some kind of error message.

spec/calculator_spec.rb

require './lib/calculator.rb'

describe Calculator do 

  before(:each) do 
    @calc = Calculator.new 
  end

  context "Given two numbers" do 
    it "can add the numbers using the add method" do
      sum = @calc.add(2,3)
      expect(sum).to eql(5)
    end

    it "can add two different numbers" do 
      sum = @calc.add(8,23) 
      expect(sum).to eql(31)
    end

    it "can subtract the numbers using the subtract method" do 
      expect(@calc.subtract(6,4)).to eql(2)
    end

    it "can multiply the numbers using the multiply method" do 
      expect(@calc.multiply(3,4)).to eql(12)
    end

    it "can divide the numbers using the divide method" do 
      expect(@calc.divide(18,3)).to eql(6)
    end

    it "gives a warning if try to divide by zero" do 
      expect(@calc.divide(2,0)).to eql("You can't divide by zero!")
    end

    it "can provide the remainder when dividing two numbers using the modulo method" do 
      expect(@calc.modulo(15,5)).to eql(0)
    end

    it "can square the result of a number" do 
      expect(@calc.square(2)).to eql(4)
    end

    it "can find the squareroot of a given number" do 
      expect(@calc.squareroot(9)).to eql(3)
    end

    it "can find the factorial of a given number" do 
      expect(@calc.factorial(3)).to eql(6)
    end 
  end
end
Enter fullscreen mode Exit fullscreen mode

lib/calculator.rb

class Calculator

  def add(x,y)
    return x + y
  end

  def subtract(x,y)
    return x - y
  end

  def multiply(x,y)
    return x * y
  end

  def divide(x,y)
    if y == 0 
     return "You can't divide by zero!"
    end
    return x / y
  end

  def modulo(x,y)
    return x % y
  end

  def square(x)
    return x * x
  end

  def squareroot(x)
    return Math.sqrt(x).round()
  end

  def factorial(x)
    result = 1
    while (x > 0)
      result = result * x
      x -= 1
    end
    return result
  end

end
Enter fullscreen mode Exit fullscreen mode

To see the file structure and folders you can look at my github repo. Check out Tutorialspointfor another good tutorial and Relish for more on the before hook.

I hope this is helpful for people just dipping their toes into RSpec. Any RSpec pros out there want to comment on ways to improve the tests I wrote, feedback is much appreciated!

Happy coding!

Top comments (4)

Collapse
 
techdecoderjonny profile image
Jonny Pabon

Going to be building this :)

Collapse
 
mmcclure11 profile image
Meks (they/them)

Nice! It was pretty fun to spin up! :D

Collapse
 
recursivefaults profile image
Ryan Latta

This is a great article and I hope tons of people get to see it. It does a great job explaining and showing a solid TDD flow!

Collapse
 
mmcclure11 profile image
Meks (they/them)

Wow, thanks, Ryan! Really appreciate the feedback!