6 Line Micro Testing Framework.

Pollux and Castor by Rogelio Bernal Andreo

Normally, I try to write tests for every project I participate in, even personal projects that never see the light of day! Yet, every once in a while, I find myself working in an environment where adding new dependencies isn’t so straightforward or tools like RSpec are a bit too bulky. In such cases, I quickly produce a few functions that allow me to execute automated tests.

In Ruby, it’s quite compact and pleasant. Here is an example, at a whopping 6 lines:

1module MT
2  def self.assert(desc, left, operator, right = nil) = puts (if msgs = self.send(operator, desc, left, right) then failure(msgs) else success(desc) end)
3  def self.test(desc, &block) ; puts desc ; yield ; puts "\n" end
4  def self.success(msg) = "  \e[32m#{msg}\e[0m"
5  def self.failure(msgs) = "  \e[31m#{msgs.join("\n    ")}\e[0m"
6end

I sliiightly lied, as it’s not very useful on its own. It indeed works as a micro “framework” so we need to add our own assertions. We can open the module up and define our own.

An assertion is defined as a method that accepts three arguments: a description, a left comparison, and a right comparison. Its return value should be nil on success and a list of failure messages on failure.

For example:

1module MT
2  def self.equals(desc, left, right)
3    return nil if left == right
4    ["#{desc} failed.", "Expected: #{right}", "Received: #{left}"]
5  end
6end

This can be compacted into a single elegant line with ruby 3! We’ll do that and add a few more:

1module MT
2  def self.equals(desc, left, right) = (["#{desc} failed.", "Expected: #{right}", "Received: #{left}"] unless left == right)
3  def self.doesnt_equal(desc, left, right) = (["#{desc} failed.", "Expected: #{left} and #{right} not to be the same"] if equals(desc, left, right))
4  def self.contains(desc, left, right) = (["#{desc} failed.", "Expected: #{left} to contain #{right}", "Received: #{left}"] unless left.include?(right))
5  def self.is_a(desc, left, right) = (["#{desc} failed.", "Expected: #{left} is a #{right}", "Received: #{left.class}"] unless left.is_a?(right))
6end

Test Run

 1#  basic_comparisons.rb
 2MT.test "Basic comparisons" do
 3  MT.assert("Comparing values", 1, :equals, 1)
 4  MT.assert("Array contents", [1, 2, 3], :contains, 3)
 5  MT.assert("Class types", "Hello world", :is_a, String)
 6  MT.assert("true is truthy", true, :is_truthy)
 7  MT.assert("objects are truthy", "Technically an object", :is_truthy)
 8  MT.assert("false is falsey", false, :is_falsey)
 9  MT.assert("nil is falsey", false, :is_falsey)
10  MT.assert("nil is nil", nil, :is_nil)
11  MT.assert("Strings can be matched", "Foo bar", :matches, /Foo/)
12end
13
14
15
16class Dog < Animal
17  def bark
18    "Woof! Woof!"
19  end
20end
21
22MT.test "Dog" do
23  dog = Dog.new
24
25  MT.assert("it is an Animal", dog, :is_a, Animal)
26  MT.assert("it woofs when it barks", dog.bark, :equals, "Woof! Woof!")
27end

Executing them yields a nice green run with our assertions categorized under our tests: MT Execution with Successes

Flipping some values shows failure messages: MT Execution with Failures

Conclusion

And that’s it! A dependency free, simple micro testing framework that can be dropped into any project. Particularly useful when installing dependencies isn’t practical.

A full copy of the framework and assertions can be found in this gist.