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)
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
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
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
Let's cd into the folder and open it up with your code editor.
$ cd calculator
$ code .
Next we are going to need a Gemfile that knows which version of RSpec to run.
$ touch Gemfile
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
and your gemfile will be updated to have the latest version of rspec available to your project.
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
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'
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
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
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
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
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
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
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
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
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
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!
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
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
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)
Going to be building this :)
Nice! It was pretty fun to spin up! :D
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!
Wow, thanks, Ryan! Really appreciate the feedback!