Mini Series
- TestUnit - Writing Test Code In Ruby (1/3)
- MiniTest - Writing Test Code In Ruby (2/3)
- RSpec - Writing Test Code In Ruby (3/3)
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'"
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
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
Here we can see assert
takes two parameters for input:
- boolean - which is required
- 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"
Flunk
This is the function signature for flunk
#flunk(message = "Flunked") ⇒ Object
Here we can see flunk
takes one parameter for input:
- 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"
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
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
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
It will output the results of the tests.
It will execute each instance function in the HelloTest
class that begins with test_
- test_world
- 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)
You have error you are return 'world2' but you are testing world in your assert_test case.
but in your test
You should change it to world2 or in first function world
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