- Explore the RSpec testing environment
- Read and interpret tests
- Practice declaring and using variables
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
learnto 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
learnagain to check your progress - Repeat until all tests are passing
- Run
learn submitto 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.
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.
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.
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
enddescribe 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
endThe 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)
endReading 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.
expectis another RSpec method, indicating a test statementfirst_numberis the variable defined two lines prior. It is the subject of the testtoallows the test to define a positive expectation. Usingnot_tohere would define a negative expectationbe_an/be_aare known as RSpec matchers. In this case, they are for setting up the expectation that something is a certain data typeorallows for two possible passing scenarios here: eitherfirst_numberis an integer orfirst_numberis 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.
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. Thesumvariable is the result of addingfirst_numberandsecond_numbertogether. This test is using all three variables. Not only that, the test is using whatever values you assigned tofirst_numberandsecond_number. -
The fourth, fifth and sixth tests are similar to the tests for
sum. Create the variabledifferencefor subtracting,productfor multiplying, andquotientfor dividing thefirst_numberandsecond_numbervariables.
Hint: If you're stuck on a particular variable, try writing the variable in
calculator.rband 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-fastto change this behavior on any lab. Just typelearn --fail-fastwhen running the tests.
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.