Ruby

We use minitest as a testing framework for Ruby coding exercises.

Hello, World!

Before we go into details, let's see how you can use minitest to test the most basic Ruby task: writing a function that returns "Hello, World!".

This is what a student might write (hello.rb):

def hello_message
"Hello, World!"
end

And this is an example of what you could write to evaluate the student's code:

require "minitest/autorun"
require "./hello"
class Evaluate < Minitest::Test
def test_hello_message
assert_equal "Hello, World!",
hello_message,
"Your function should return: Hello, World!"
end
end

Let's examine the above evaluation code.

We first need to require the minitest framework and the user file(s).

Minitest::Test

Then we need to define a suite of unit tests as a class that inherits from Minitest::Test.

This groups together multiple unit tests and allows implementing common functionality that you want executed before and after each test, via the setup() and teardown() methods respectively.

Unit tests can be implemented as methods inside this class that start with test_. These are the only functions that will be executed when running the evaluation code.

class Evaluate < Minitest::Test
def setup
# This code will be executed before every unit test.
end
def teardown
# This code will be executed after every unit test.
end
def test_something
# This is a unit test.
end
def test_sometings_else
# This is another unit test.
end
end

Assertions

Assertions are the statements that actually verify that the student's code behaves as expected. The most common pattern is to call student's methods and assert that their return value is the one that you expect. If it is not, then the test will fail and you have the opportunity to provide a custom message to the student for each failed assertion, so that she can improve.

These are the most common ones:

assert(boolean_value, msg = nil)
assert_equal(expected_value, actual_value, msg = nil)

The assertion from the Hello World example could be rewritten as:

assert "Hello, World!" == hello_message,
"Your function should return: Hello, World!"

If any of the assertions in your unit tests fail, the test is marked as failed and feedback (the msg parameter) is given to the student.

Standard output

In the previous example we had the students return Hello, World!, but actually a more common task is to have them print the same string to the standard output.

def say_hello
puts "Hello, World!"
end

How do we test this specific function?

require "minitest/autorun"
require "./hello"
class Evaluate < Minitest::Test
def test_hello_message
out, err = capture_io do
say_hello
end
assert_equal "Hello, World!\n", out, "Your function should return: Hello, World!"
end
end

Minitest provides the capture_io method which captures and returns standard output and standard error for all the statements that are executed within the method's block.

Technical specs

  • Ruby >= 2.3.1

  • Minitest >= 5.9.0

Example 1: FizzBuzz

Given:

  • An integer number n

Your task is to:

  • Write a function that returns an array with the numbers from 1 to n with the following restrictions:

    • for multiples of 3 store "Fizz" instead of the number

    • for multiples of 5 store "Buzz" instead of the number

    • for numbers which are multiples of both 3 and 5 store "FizzBuzz"

Example:

fizzbuzz(15) == [
1, 2, "Fizz", 4, "Buzz",
"Fizz", 7, 8, "Fizz", "Buzz",
11, "Fizz", 13, 14, "FizzBuzz"
]

Here's how a student might solve the problem:

def fizzbuzz(n)
result = []
(1..n).each do |i|
if i % 15 == 0
result.push "FizzBuzz"
elsif i % 3 == 0
result.push "Fizz"
elsif i % 5 == 0
result.push "Buzz"
else
result.push i
end
end
result
end

Here's how you might test the student's solution:

require "minitest/autorun"
require "./fizzbuzz"
class Evaluate < Minitest::Test
def test_fizzbuzz_simple
assert_equal [1], fizzbuzz(1), "fizzbuzz(1) should return [1]"
end
def test_fizzbuzz
result = fizzbuzz(15)
assert result.kind_of?(Array), "Your function should return an array"
expected = [
1, 2, "Fizz", 4, "Buzz",
"Fizz", 7, 8, "Fizz", "Buzz",
11, "Fizz", 13, 14, "FizzBuzz"
]
assert_equal expected.length,
result.length,
"Your function should return #{expected.length} objects"
result.each_with_index do |obj, idx|
assert_equal expected[idx],
obj,
"Your function should map #{idx + 1} to #{expected[idx]} instead of #{obj}"
end
end
end

Example 2: Title Case

Given:

  • A sentence with words separated by spaces

Your task is to:

  • Write a function that returns a copy of this sentence where all the words:

    • start with a capital letter

    • the rest of the letters are lower case

Note:

  • Your function should not modify the sentence given as argument.

Example:

normalize("This is SO MUCH FUN! ") == "This Is So Much Fun! "

Here's how a student might solve the problem:

def normalize(sentence)
copy = sentence.dup
idx = 0
while idx < copy.length
idx = skip_whitespace copy, idx
idx = normalize_word! copy, idx
end
copy
end
def skip_whitespace(sentence, idx)
while idx < sentence.length and sentence[idx] == ' '
idx += 1
end
idx
end
def normalize_word!(sentence, idx)
if idx < sentence.length and sentence[idx] != ' '
sentence[idx] = sentence[idx].upcase
idx += 1
end
while idx < sentence.length and sentence[idx] != ' '
sentence[idx] = sentence[idx].downcase
idx += 1
end
idx
end

Here's how you might test the student's solution:

require "minitest/autorun"
require "./normalize"
class Evaluate < Minitest::Test
def test_character
assert_equal "A", normalize("a"), "Your function should convert a single character"
assert_equal "A", normalize("A"), "Your function should convert a single character"
end
def test_no_mutate
sentence = "a"
normalize sentence
assert_equal "a", sentence, "Your function should not mutate the argument"
end
def test_word
assert_equal "Abc", normalize("abc"), "Your function should convert a single word"
assert_equal "Abc", normalize("ABC"), "Your function should convert a single word"
assert_equal "Abc", normalize("aBC"), "Your function should convert a single word"
assert_equal "Abc", normalize("Abc"), "Your function should convert a single word"
end
def test_sentence
assert_equal "Abc Def Ghi",
normalize("abc def ghi"),
"Your function should convert a sentence"
end
def test_multiple_whitespace
assert_equal " Abc Def Ghi ",
normalize(" abc def ghi "),
"Your function should keep multiple whitespaces"
end
end