The Crystal Programming Language Forum

Athena Spec Component

I really like the Spec module in regards to its simplicity. It makes writing simple tests a breeze. However, if you want to reuse some tests between n common types; it gets a bit trickier/messier. There are shards out there that provide a more unit test styled approach that can help with this, such as Minitest. But these are standalone testing frameworks which, of course, bring their own dependencies and methodologies.

I had the idea to combine the benefits of these approaches into a shard. Take the simplicity and already established assertion methods/features of the stdlib’s Spec module and combine it with a more OOP way of writing them. The result of is Athena::Spec.

At the moment the shard has two main features:

A test case is a struct that inherits from a base struct. Tests are methods that start with test_. For example:

struct ExampleSpec < ASPEC::TestCase
  @target : Calculator

  def initialize
    @target = Calculator.new
  end

  def test_add
    @target.add(1, 2).should eq 3
  end

  def test_subtract
    @target.subtract(10, 5).should eq 5
  end
end

ExampleSpec.run

When it runs, it essentially is the same as:

describe ExampleSpec do
  it "add" do
    Calculator.new.add(1, 2).should eq 3
  end

  it "subtract" do
    Calculator.new.subtract(10, 5).should eq 5
  end
end

The important thing to know is this is NOT a standalone testing framework. It is just an alternate syntax that can be used to generate the standard describe, it, and pending methods while also using the same assertion style as the stdlib’s Spec module.

The main benefits of this approach is that you are able to use the full feature set of OOP, including Inheritance. Any tests defined in parent tests cases will run for each child. This allows creating test cases/helper methods for similar objects and reusing the same base test case for each. Abstract methods, super, etc may also be used to abstract even more logic into the base test cases.

The spec component also comes with a new feature, DataProviders. Data providers can be used to supply arbitrary input arguments to your test methods, without needing to duplicate the actual test’s logic.

struct DataProviderTest < ASPEC::TestCase
  @[DataProvider("get_values")]
  def test_squares(value : Int32, expected : Int32) : Nil
    (value ** 2).should eq expected
  end

  def get_values : NamedTuple
    {
      two:   {2, 4},
      three: {3, 9},
    }
  end
end

DataProviderTest.run

This would result in two it blocks, one for each “set” of data.

Since these test cases boil down to standard Spec methods, they can be used in conjunction with non ASPEC::TestCase specs. This allows using the stdlib’s approach for simple tests, while being able to define more reusable test cases when/if needed. An example of this is shards could define the tests case in their source code to use, but also expose them for end users to use in their tests.

As usual feel free to join me in the Athena Gitter channel if you have any suggestions, questions, or ideas. I’m also available on Discord (Blacksmoke16#0016) or via Email.

EDIT: Updated for https://github.com/athena-framework/spec/releases/tag/v0.2.0, had a minor breaking change.

2 Likes