Reading RSpec Tests

Learning Goals

  • Explore the RSpec testing environment
  • Read and interpret tests
  • Practice declaring and using variables

Introduction

We've covered a lot of the basics of Ruby, but, before we go further, we must talk more about testing. As part of this course, you will encounter many lessons with tests that must be passed to register the lesson as complete. These are referred to as labs. You've already completed a few of them! In all labs, you follow a similar process:

  • Work in the provided files, testing out potential solutions
  • Run learn to print the tests at any point while you're writing your code
  • Read the error messages produced by running the tests
  • Write code that will resolve these error messages
  • Run learn again to check your progress
  • Repeat until all tests are passing
  • Run learn submit to submit your solution

As the lesson topics become more complicated, so do the tests. Getting acquainted with reading and interpreting tests will help you overcome some of the toughest labs ahead. In this lesson, we're going to walk through reading tests in detail while also getting a little more practice with variables.

Building a Simple Calculator

Our task in this lab is to build a simple calculator. You might be thinking you just did this. You are correct, and this calculator is even simpler than the last. The goal here, though, isn't to teach you a new concept you need to practice writing. The goal in this lab is to get better at reading, and since we've already tackled a similar problem, we can focus on reading the tests.

In Ruby, tests are typically handled by a tool called RSpec. RSpec is written in Ruby, but as we will see, has some custom functionality built in specifically for writing and running tests.

Directory Structure

The structure of this lab looks roughly like the following:

├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── calculator.rb
└── spec
    └── calculator_spec.rb
    └── spec_helper.rb

In our Ruby content, all labs will have a spec folder that contains our tests. spec/calculator_spec.rb is the file in this case. spec/spec_helper.rb is a support file that is usually the same over multiple labs and can be ignored.

Code Along

Open up calculator.rb in your text editor. In this file, you should see, well, nothing. We'll fix that soon.

Now, open up spec/calculator_spec.rb. Hey, there's something! What's all of this stuff doing?

Note: The spec/calculator_spec.rb has a lot of info that we want to look at, but do not edit this file. Otherwise, you may have extra difficulty passing this lab.

A few lines down in the spec/calculator_spec.rb file you will see:

describe "./calculator.rb" do
  # Lots of code written inside here
end

describe is a method provided by our test library, RSpec. Remember, methods let us group up a set of statements into a sort of bundle. Whenever that bundle's name is called, all the statements inside are called in order. This is incredibly useful when we need to run the same statements over and over (as we do running and rerunning tests).

The describe method holds our tests. Just after describe is a string, "./calculator.rb". Here, RSpec is telling us that the tests that come afterward will be about the file calculator.rb. We see the first one inside another RSpec method, it:

it "contains a local variable called first_number that is assigned to a number" do
  # code for first test is in here
end

The it method has a longer string describing what this test is for. In this case, since this is within the describe method, we can infer that calculator.rb contains a local variable called first_number, and that the variable is assigned to a number.

Looking at what is inside, we can see the actual test that has been described:

it "contains a local variable called first_number that is assigned to a number" do
  first_number = get_variable_from_file('./calculator.rb', "first_number")

  expect(first_number).to be_an(Integer).or be_a(Float)
end

Reading the first line, we see a variable, first_number being assigned to something, get_variable_from_file. This is actually another method! For now, we don't need to know what this method is doing (although, given its name, we could probably guess). All we need to know is that the result of get_variable_from_file('./calculator.rb', "first_number") is getting assigned to first_number.

Two lines down, we see something else that is new: expect.

expect(first_number).to be_an(Integer).or be_a(Float)

This is the actual test that will produce a passing or failing response when we run learn. Read out loud, this line sounds like a normal English sentence: Expect first_number to be an integer or be a float.

  • expect is another RSpec method, indicating a test statement
  • first_number is the variable defined two lines prior. It is the subject of the test
  • to allows the test to define a positive expectation. Using not_to here would define a negative expectation
  • be_an / be_a are known as RSpec matchers. In this case, they are for setting up the expectation that something is a certain data type
  • or allows for two possible passing scenarios here: either first_number is an integer or first_number is a float.

If we run learn this test appears first, along with the string descriptions we saw in describe and it:

./calculator.rb
  contains a local variable called first_number that is assigned to a number (FAILED - 1)

Failures:

  1) ./calculator.rb contains a local variable called first_number that is assigned to a number
     Failure/Error: raise NameError, "local variable #{variable} not defined in #{file}."

     NameError:
       local variable first_number not defined in ./calculator.rb.
     # ./spec/spec_helper.rb:14:in `rescue in get_variable_from_file'
     # ./spec/spec_helper.rb:11:in `get_variable_from_file'
     # ./spec/calculator_spec.rb:6:in `block (2 levels) in <top (required)>'
     # ------------------
     # --- Caused by: ---
     # NameError:
     #   local variable `first_number' is not defined for #<Binding:0x00007fb7db153ca0>
     #   ./spec/spec_helper.rb:12:in `local_variable_get'

What if we were to create a variable called first_number inside calculator.rb, but assigned it to something other than a number? Say, for instance, we wrote first_number = "Hello world!" in calculator.rb. Running learn again, we would see this:

./calculator.rb
  contains a local variable called first_number that is assigned to a number (FAILED - 1)

Failures:

  1) ./calculator.rb contains a local variable called first_number that is assigned to a number
     Failure/Error: expect(first_number).to be_an(Integer).or be_a(Float)

          expected "Hello world!" to be a kind of Integer

       ...or:

          expected "Hello world!" to be a kind of Float
     # ./spec/calculator_spec.rb:8:in `block (2 levels) in <top (required)>'

Notice that the error message has changed. The test was able to get the variable first_number that we defined inside calculator.rb. It then runs the test on first_number to see if it is a kind of integer or float. It isn't either, so the test fails.

That's a lot to take in. Don't worry too much yet if it's hard to understand what is happening inside of the spec/calculator_spec.rb file. But it's a good idea to open up the file and gather the information that you can, especially when you are stuck in a lab. Reading test files and their results can give targeted insight into what is breaking and where when you're writing a solution.

We will also provide instructions in the README.md file that will allow you to complete the lab. However, the better you understand the tools you are using, the better equipped you will be as we progress to more and more complex topics.

Solving the Tests for this Lab

Now that you've got a sense of the tests in this lab, it is time to solve them all. There are six tests in total. In this lab, testing is configured with --fail-fast, so you will only see the first test that fails along with any passing tests before that. This means, as you pass each test, you'll see a growing list of passing tests until you've passed them all. Run learn as you work through each step below to see the test results.

  • The first test we've started to solve already. The test is looking for a variable in calculator.rb, first_number. This variable should be set to an integer or float

  • The second test is similar, but this time, looking for second_number. However, there is a second test here that must also pass:

expect(second_number).not_to equal(0)
  • The third test is looking for a local variable named sum. The sum variable is the result of adding first_number and second_number together. This test is using all three variables. Not only that, the test is using whatever values you assigned to first_number and second_number.

  • The fourth, fifth and sixth tests are similar to the tests for sum. Create the variable difference for subtracting, product for multiplying, and quotient for dividing the first_number and second_number variables.

Hint: If you're stuck on a particular variable, try writing the variable in calculator.rb and assigning it a value you know is incorrect. Tests may produce different bits of useful information based on what you've written.

Once you have all tests passing, run learn submit to submit your solution.

Note: Many labs in this course will show all the tests, both passing and failing by default. You can use --fail-fast to change this behavior on any lab. Just type learn --fail-fast when running the tests.

Conclusion

Labs are a major part of this course and all labs rely on tests that you must pass to register the completion of the lesson. Being able to read and interpret tests will help you unravel complex challenges ahead. More than that, though, testing is a powerful tool you can use in your future development work.

Test-driven development is a common process for developing programs in which tests are written before code. A feature is first designed. Those designs are then set up as test expectations. Once the tests are created, the actual code is written to pass those tests.

Writing our own tests is still a bit further down the path of learning Ruby, but being able to read tests is a skill that will be immediately helpful to you.

Resources

Test Driven Development RSpec