DEV Community

Dane Dawson
Dane Dawson

Posted on

Ruby Newby, Testing with rspec part I

Welcome back! Today I am going to talk about testing, and why it's so important.

(As a note, this article is meant for absolute beginners with writing and performing test cases, future articles will go more in depth)

Types of testing

There are as many applications of testing as there are types of programmers, and as you delve deeper into the world of programming you will find that every person and organization has their own structure and approach to how they design and implement tests. For now let's focus on the two main approaches to writing code on a foundational level.

BDD

Behavior Driven Development -> Most likely as you begin your coding experience this is what you are used to. The programmer imagines a behavior they want to code to mimic/create, and attempts to write code that creates the results they want. For example, if I wanted a program that told me a personalized greeting when given a name I would start by trying to code something like this:

    def hello(name)
        return "Hello #{name}!"
    end

...And if it didn't work I would tweak/troubleshoot/debug until I got the response I wanted. The idea is your code is driven by the behavior you want it to perform directly.

TDD

Test Driven Development -> Write a test for the results you want to accomplish and then attempt to write the least amount of code to pass the test. Further refine your code by adding more tests to allow your code to be more precise. The following pseudocode is not syntax accurate, but gives more legible examples of a sequence of tests that one might develop to get to the same function we have as above:

    describe 'hello(name)' do
        #first test is to make sure the function returns a greeting
        it "Method hello(name) includes the word Hello" do
            expect(hello(name)).to include("Hello")
        end
        #second test ensures the greeting is personalized
        it "Method hello(name) includes the given name" do
            expect(hello("Joe")).to include("Joe")
        end
        #third test is for the precise return desired with given name
        it "Method hello(name) says "Hello (given name)!"" do
            expect(hello("Joe")).to eq("Hello Joe!")
        end
    end

In true TDD one would write the first test, then write code to suit that test, then expound further with each additional test.

The benifits of testing to systems are multifaceted, but the most common reason you will utilize tests is for debugging. All code requires maintenence and upkeep, and especially as you gain more experience and contribute to more projects it will be literally impossible to remember the nuance or specificity about why you have code written the way you do...not to mention, each individual project may grow/evolve in a way that it's easy to forget the minutiae that interconnects all your methods and functions.
Adding tests to your project gives you means to verify existing code you modify or new code you add to your project doesn't alter the existing code base in a way that you aren't expecting. Although best code practices help minimize the likelihood of this kind code interference it's impossible to remember the way every single line of your code interacts with every other line of code throughout your whole project. Built in tests give you a way to add a series of checks that can be run as frequently as you want to make sure that your project is growing in the direction you want.

Implementing

I could (and maybe will eventually) write a whole article about the history of tests and the ways they are used by organizations of various sizes, but for now I just want to focus on the very basics of test writing.

I will be using rspec for my examples, and will currently be operating under the assumption you already know how to install and set up rspec within your code. If not, here is an example blog post about setting up your rspect environment.

The basic outline for creating a test in rspec is as follows:

RSpec.describe ClassObject do
    describe '#new' do
        it "Generates a new ClassObject with name Hello World" do
            newObject = ClassObject.create(:name => "Hello World")

            expect(newObject.name).to eq("Hello World")
        end
    end
end

The test is wrapped in RSpec.describe (name of the model/object you are testing) do->end. Within the test you can create describe blocks, which will create an ExampleGroup. This is basically a class in which "it" blocks are passed and evaluated, each block existing within a unique instance of that tested class. Every "it" method within the describe block becomes a test case in which you give the parameters of your test and your expected results. This article is solely to introduce the base form of testing, but feel free to look here at one of the more comprehensive syntax/vocabulary guides I have found regarding rspec's many applications.

I have created a very simple series of tests to show initial implementation and foundation for rspec testing. If you were to create these files in a ruby repository with the rspec gem installed they can be run and shown to pass.

First is the class, which I have classed Thingdoer

#thingdoer.rb
class Thingdoer

    #The first test is set to see if the method equals "Hello". 
    #Simple test just to show framework and how to use expect().to
    def return_hello
        return "Hello"
    end

    #This second function has the same test written and also passes. 
    #This works because Ruby implicitly returns 
    def hello
        "Hello"
    end

    #This test would fail with the the previous testing parameter, 
    #because although it puts "Hello", the method itself does 
    #not return "Hello". The test has been changed to pass by 
    #altering the expect().to into expect().not_to
    def hello_fail
        puts "Hello"
    end

    #One more example to show that the last string in the 
    #function is the one returned, the test for this function 
    #checks for "Goodbye".
    def return_goodbye
        "Hello"
        "and"
        "Goodbye"
    end

    #This test shows a way to use include testing. 
    #Use include to specify that a collection includes 
    #one or more expected objects. Note that even though 
    #there are two entries, include only tests if *any* 
    #object passes the condition
    def say_hello
        message = ["Hello", "and", "Goodbye"]
    end

end

And as is required by rspec, the tests are saved within the spec folder and following naming requirements.

#spec/thingdoer_spec.rb
require 'thingdoer'

RSpec.describe Thingdoer do
    describe '#hello' do
        it 'Method hello should equal Hello' do
            expect(Thingdoer.new.hello).to eq("Hello")
        end
    end


    describe '#return_hello' do
        it "Method return_hello returns value of 'Hello'" do
            expect(Thingdoer.new.return_hello).to eq("Hello")
        end
    end

    describe '#return_goodbye' do
        it 'Method return_goodbye should equal GoodBye' do
            expect(Thingdoer.new.return_goodbye).not_to eq("Hello")
            expect(Thingdoer.new.return_goodbye).not_to eq("and")
            expect(Thingdoer.new.return_goodbye).to eq("Goodbye")

        end
    end

    describe '#hello_fail' do
        it 'Method hello_fail should *not* equal Hello' do
            expect(Thingdoer.new.hello_fail).not_to eq("Hello")
        end
    end

    describe '#say_hello' do
        it 'Method say_hello should include Hello' do
            expect(Thingdoer.new.say_hello).to include("Hello")
            expect(Thingdoer.new.say_hello).not_to include("Welcome")
        end
    end

end

Hopefully this will make it a little easier for someone else trying to get started writing tests, I know my first test was an incredibly overwhelming undertaking. I am in the process of creating a simple rails app with testing at all the various levels for upcoming blog posts, so hopefully I will see you back here again soon!

Happy coding!

Latest comments (0)