DEV Community

Cover image for TestUnit - Writing Test Code In Ruby (1/3)
Andrew Brown 🇨🇦 for ExamPro

Posted on

TestUnit - Writing Test Code In Ruby (1/3)

Mini Series

Prelude to Learning
I'm training my juniors on how to write test code since we need to improve our code coverage in ExamPro. I had quickly put together three guides last Sunday on TestUnit, MiniTest and RSpec. Why all three? I believe that learning by variation results in complete knowledge.

I figured I should publish these three articles on DEV.to articles "as is" otherwise I'll forget about them, they'll end up in my "Dump" folder, never to been seen again.

p.s. I had written challenges to each of these guides. I don't know if people want them. Show me enough reactions, comment below if you want them, and I'll find the time to publish them if there's interest.

TestUnit

TestUnit is a standard ruby library.
Standard ruby libraries mean you do not have to install a gem because it's already included with the language.

The simplest example of using TestUnit is as follows.

require "test/unit/assertions"
include Test::Unit::Assertions

hello = 'world'

assert_equal 'world', hello, "hello function should return 'world'"
Enter fullscreen mode Exit fullscreen mode

Assertion Functions

assert_equal is one of many assertion functions.
Assertion functions are part of all of the testing libraries to assert whether
something will pass or fail. The assertion is your "test".

Here is a list of all assertion functions which are part of TestUnit.
In practicality, you will be using a handful of these assertion functions.

assert
assert_block
assert_boolean
assert_compare
assert_const_defined
assert_empty
assert_equal
assert_fail_assertion
assert_false
assert_in_delta
assert_in_epsilon
assert_include
assert_instance_of
assert_kind_of
assert_match
assert_nil
assert_no_match
assert_not_const_defined
assert_not_empty
assert_not_equal
assert_not_in_delta
assert_not_in_epsilon
assert_not_include
assert_not_match
assert_not_nil
assert_not_predicate
assert_not_respond_to
assert_not_same
assert_not_send
assert_nothing_raised
assert_nothing_thrown
assert_operator
assert_path_exist
assert_path_not_exist
assert_predicate
assert_raise
assert_raise_kind_of
assert_raise_message
assert_raises
assert_respond_to
assert_same
assert_send
assert_throw
assert_throws
assert_true
build_message
flunk
Enter fullscreen mode Exit fullscreen mode

Assert and Flunk

The most basic assertion functions are assert and flunk.

A function signature defines the inputs and outputs of a function.
You can find functions signatures in the technical documentation for
your language and libraries. Let's look at the function signature for
'assert and flunk which are defined on RubyDocs.

Assert

This is the function signature for assert

#assert(boolean, message = nil) ⇒ Object
Enter fullscreen mode Exit fullscreen mode

Here we can see assert takes two parameters for input:

  1. boolean - which is required
  2. message - which is optional

And it outputs an Object.

This is how we could use this function

# simple.rb
require "test/unit/assertions"
include Test::Unit::Assertions

x = true

assert x, "x should pass"
Enter fullscreen mode Exit fullscreen mode

Flunk

This is the function signature for flunk

#flunk(message = "Flunked") ⇒ Object
Enter fullscreen mode Exit fullscreen mode

Here we can see flunk takes one parameter for input:

  1. message - which defaults to "Flunked" if no value supplied

And it outputs an Object.

This is how we could use this function

# flunk.rb
require "test/unit/assertions"
include Test::Unit::Assertions

flunk "throw a failure message"
Enter fullscreen mode Exit fullscreen mode

The purpose of flunk is when you a test that always fails.
You may not find practical utility in flunk

How to Properly Write tests with TestUnit

The examples above were the fewest amounts of lines to use TestUnit but this is not the correct way.
The correct way is to create a separate file.
This new test file will require the code we wish to test and we will need to create to TestCase class to then write test functions.

Hello World TestCase Example

We'll create a simple class within its own file called hello.rb.
This class will have a class method called self.hello which will return a string.

# hello.rb
class Hello
  def self.world
    'world2'
  end
end
Enter fullscreen mode Exit fullscreen mode

We will create a new file called hello_test.rb
Notice that we gave the file name the exact name and then appended _test to the end it.
You will find later when you have more test files you will want to stick to this convention because of automation tools.

# hello_test.rb
require "test/unit"
require_relative './hello'

class HelloTest < Test::Unit::TestCase
  def test_world
    assert_equal 'world', Hello.world, "Hello.world should return a string called 'world'"
  end

  def test_flunk
    flunk "You shall not pass"
  end
end
Enter fullscreen mode Exit fullscreen mode

The class we defined is not a normal class but is a Domain Specific Langauge (DSL).
A DSL is when you change the behaviour of your code to act as a language within a language.
The ruby language is a very malleable language and as a result, it easy to write DSLs.
The ruby community frequently says code is "magic" when referring to DSLs.
The rules of how a DSL is not always clear which can lead to common confusion.

The HelloTest class extends Test::Unit::TestCase which is where the "magic" that turns this class into a DSL.
When this file is executed eg.

ruby hello_test.rb
Enter fullscreen mode Exit fullscreen mode

It will output the results of the tests.
It will execute each instance function in the HelloTest class that begins with test_

  1. test_world
  2. test_flunk

It the instance function is not named with test_ then it will not run.
This is part of the rules of this DSL.

Thoughts on TestUnit

TestUnit is not the only testing frameworks in ruby since we also have MiniTest and Rspec.
These other testing frameworks have different tradeoffs but are similar in functionality.

TestUnit is the old and simple test framework.

In the upcoming lectures, we are going to explore both MiniTest and Rspec and
learn the trade-offs.

All frameworks are used, it is hard to say which is most commonly used
and it comes down to team preference.

Code

References

https://stackoverflow.com/questions/12317921/why-undefined-method-assert-equal-is-thrown-even-after-requiring-test-unit
https://apidock.com/ruby/Test/Unit/Assertions
https://www.rubydoc.info/gems/test-unit/2.3.0/Test/Unit/Assertions
https://stackoverflow.com/questions/6515333/how-do-i-execute-a-single-test-using-ruby-test-unit
https://mattbrictson.com/minitest-and-rails

Top comments (2)

Collapse
 
kamalpanhwar profile image
Kamaluddin Panhwar

You have error you are return 'world2' but you are testing world in your assert_test case.

def self.world
    'world2'
  end
Enter fullscreen mode Exit fullscreen mode

but in your test

  assert_equal 'world', Hello.world, "Hello.world should return a string called 'world'"
Enter fullscreen mode Exit fullscreen mode

You should change it to world2 or in first function world

Collapse
 
artoodeeto profile image
aRtoo

hello sir. this article is awesome. may I ask whats the ruby version you are using? seems different than mine. I have a ActionController::TestCase