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:
-
Athena::Spec::Methods provides some common/helpful test helper methods that could be included into any
spec_helper
- This includes ways to test compile errors (from Testing compile time errors) and running executables.
-
Athena::Spec::TestCase provides a way to write normal
Spec
based tests in a more OOP fashion
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.