Intro to Testing

Back

Learning Goals

  • Explain what a test is (in software development)
  • Read an rspec test and explain what it is asking for
  • Write simple Ruby classes that meet requirements of pre-written tests

Vocabulary

  • assertion
  • rspec
  • Test Driven Development
  • test
  • testing framework

Tests

In software, it is common to write automated tests to verify that our code behaves the way we want it to and doesn’t have any negative or unexpected side effects (bugs) on any other part of a project. These tests are usually written in the same language the code itself is in, with the help of a tool that is usually categorized as a testing framework.

File Structure

There are many ways a file structure can be designed when there are many files in a project. A common convention, and one you’ll use in Mod 1 is illustrated:

project_name
|-lib
  |-name_of_class.rb
  |-name_of_class1.rb
|-spec
  |-name_of_class_spec.rb
  |-name_of_class1_spec.rb

(Note: The directory and file names do not start with |-; that is just a way to notate the structure.)

rspec

rspec is a tool, classified as a testing framework, that allows us to write automated tests that will test our Ruby code. Just like we must write Ruby exactly as it’s intended to be used, we must carefully use the syntax and methods available within rspec. The official rspec documentation may at first seem dense; it has much more than what you will even get into in Mod 1, but it’s great to be aware of and start getting comfortable reading if you need a resource during or after this lesson.

Setup rspec

While navigated to the project that is applicable, run this command in the Terminal to add the rspec framework to that project. This command only needs to be run once per project.

$ gem install rspec

The first two lines of every test (or spec) file should be:

require 'rspec'
require './lib/<name_of_class>.rb'

To run a test, or spec, file, in the Terminal:

rspec <name_of_class_spec>.rb

Documentation Reading

There are times when you need to follow directions exactly, and times when you might need to adjust to add in custom information. In the previous examples, we can infer that name_of_class_spec is not actually a file name, but the documention is intending to show the reader that the last part of the file name should be _spec, preceded by the name of the class the test file will be testing.


Syntax and Anatomy of an rspec Test

A series of code snippets is provided to show a progression of a test file being built. In Mod 0, you do not need to be proficient in writing tests; this progression is shown to help you focus on each piece with a paired explanation.

The code snippet that follows shows the skeleton of an rspec test. The class it is testing is named Student, and lives in a file with the same naming convention.

After requiring rspec and the code file, a describe block is opened up, where rspec expects the class name. The do and end open and close the block, similar to other Ruby structures. The code snippet that follows is not enough to test any code; but it’s the first step:

# student_spec.rb
require 'rspec'
require './lib/student'

describe Student do

end

In the next code snippet, one test has been added inside of the describe block, to test a very small thing - this test verifies that when an object instance is created, it is actually an instance created from the class it should be created from.

This describe block contains:

  • An it statement - a String that uses plain English to describe what it (the test) tests
  • Definition of a variable that stores a student object
  • An assertion written with rspec
# student_spec.rb
require 'rspec'
require './lib/student'

describe Student do
  it 'is an instance of student' do
    student = Student.new('Penelope')
    expect(student).to be_a Student
  end
end

The final snippet adds a test (in a new it block) that checks if a student object instance has a name attribute that matches the value that was passed in upon creating the object instance:

# student_spec.rb
require 'rspec'
require './lib/student'

describe Student do
  it 'is an instance of student' do
    student = Student.new('Penelope')
    expect(student).to be_a Student
  end

  it 'has a name' do
    student = Student.new('Penelope')
    expect(student.name).to eq 'Penelope'
  end
end

The following code snippet provides an annotation for each line of this same file:

# ensures the rspec testing framework is available for use in this file
require 'rspec'
# allows the spec file to read the contents of the student file
require './lib/student'

# start of describe block; one per class/test file
describe Student do
  # start of it block for an individual test
  # the string should briefly describe in plain English what is being tested
  it 'is an instance of student' do
    # create a student object instance
    student = Student.new('Penelope')
    # assert that the student is from the Student class
    expect(student).to be_a Student
  end

  it 'has a name' do
    student = Student.new('Penelope')
    # assert that the student has a name property which matches what was passed in
    expect(student.name).to eq 'Penelope'
  end
end

The code that follows would allow the previous tests to pass (which should live in the student.rb file):

class Student
  attr_reader :name

  def initialize(name)
    @name = name
  end
end

Explore rspec

Follow the directions to set up a small project that uses rspec:

  • Create a directory called intro_testing
  • Install the rspec gem using the command noted earlier in the lesson
  • Create two directories: lib and spec
  • Create one file inside of each of the new directories: student.rb and student_spec.rb respectively
  • Copy and paste the code for the Student class and the two tests that are provided above, into the appropriate files
  • Run rspec spec/student_spec.rb in your Terminal
  • Delete the attr_reader from the Student class and re-run the tests; observe what happens
  • Delete the entire initialize method from the Student class and re-run the tests; observe what happens
  • Delete the entire Student class and re-run the tests; observe what happens
  • Bring back all the original code into the Student file and re-run the tests to ensure they now pass

Challenge: Write a new test, that checks that the student has a dynamic age property. Write the code to make the test pass.

Test Driven Development

Test Driven Development is a common process for writing software. It entails writing the tests before the code, then using the test to guide the developers while writing the actual code. This lesson will provide some exposure to the process, and the Mod 1 curriculum will dive deep into it and require it of students.

When provided the test file that follows (the same one we’ve been looking at in previous examples), one can turn that code into directions that inform what should be written into the Student class.

require 'rspec'
# write your code in a file named student
require './lib/student'

describe Student do
  it 'is an instance of student' do
    # since a student object is being created from a Student class,
    # write a class named Student

    # ALSO - since an argument is being passed to Student, the initialize method needs to accept one
    student = Student.new('Penelope')
    expect(student).to be_a Student
  end

  it 'has a name' do
    student = Student.new('Penelope')
    # since we need to call the name attribute and get back the string that was passed in,
    # we need an attr_reader for the name attribute
    expect(student.name).to eq 'Penelope'
  end
end

The comments in the file can help a developer write an even more accessible specification:

  • Write a class named Student, in the lib/student file
  • The class should have one dynamic attribute named name
  • Use an attr_reader so the name can be read

With that, the following code can be written to satisfy the tests:

class Student
  attr_reader :name

  def initialize(name)
    @name = name
  end
end

Practice Reading Tests

Read the test in this file and write out a list of human-readable, clear directions you could read aloud to someone you are pairing with in order to pass the tests. One of the tests does push you to apply some learning that was not explicitly covered in this less - that was intentional! The goal is not to be perfect or perfectly correct; it's to push you to apply some other knowledge and start getting comfortable with unknown code.

Check For Understanding

Create a new directory with lib and test directories inside. Use the test file that was provided in the previous activity, and write a class that satisfies those tests. Use your Git Workflow, make a GitHub repository and push your completed work up.

Mod 0 Extensions

Your Mod 1 instructors have created this repository to give you something to work on in between Mod 0 and Mod 1. Nothing is required; but students in the past have found it as helpful and aligned practice during the “down time” before Mod 1. Have fun!





Back