Sean

Neospec

- 4 minutes to read

What is it?

Neospec is a testing library I’ve built for both Ruby and MRuby. I’ve tried some more experimental things with this. It’s also been a great learning experience.

What does it look like?

require "neospec"

unit = Neospec::Suite.new
neospec = Neospec.new(suites: [unit])

unit.describe "An example test" do
  Given "apples are ripe" do
    @apples = :ripe
  end

  And "bananas are ripe" do
    @bananas = :ripe
  end

  Then "apples and bananas are as ripe as each other" do
    expect(@apples).to_equal(@bananas)
  end

  But "the bananas become TOO ripe" do
    @bananas = :over_ripe
  end

  Then "apples and bananas are now not as ripe as each other" do
    expect(@apples).not_to_equal(@bananas)
  end
end

neospec.run!

Example output of a test run

Why build this?

  1. It seemed fun
  2. I wanted something for Taylor
  3. I think a more modular approach makes more sens over RSpec/Minitest

Neospec breakdown

diagram.excalidraw.svg

The top level Neospec object has a Neospec::Logger for printing out the results of each Neospec::Spec, I’m not completely convinced on this structure, it feels like it should be on the Neospec::Suite itself but I didn’t want to have to define it for every Neospec::Suite in a project and didn’t really see much value in allowing it to be different for each Neospec::Suite.

The Neospec::Reporter concept comes from how Rspec does many different —format flags. That always annoyed me and seemed counter intuitive, this way how your specs are reported to both the end user and the continuous integration system are defined in code rather than some dot file.

Neospec::Suite is where it gets a bit more interesting, here we define what sort of Neospec::Runner we want, this is basically an execution strategy. The only one right now is just sequential, but I see a future where threaded or ractor based runners are used for unit tests and maybe integration. This is also where all the hooks are defined. You’ve got your setup and teardown which run before and after all the tests run but only once and you’ve got before and after which run before and after each test.

I’ve always found lots of clutter in an RSpec configuration block due to the wide range of types of tests run within it. You end up doing all these tag checks before every test, or superfluously running things like DatabaseCleaner before unit tests. This completely separates those concerns and you’ll always know exactly what specs will be run in that environment.

Neospec::Spec contains quite a bit of interesting code, this will probably see the most change and I’ll almost certainly end up with some sort of Neospec::Spec::Context to help prevent leaking internal state to the tests, which I’ve had to rename my instance variables to make as hard as possible to do accidentally. this is where you get all these lovely Given, And, Then, When, etc blocks from. They’re just syntactic sugar for making readable tests and are completely optional.

I have deliberately not allowed nesting specs. This has also made it easy to write code that knows exactly how many tests there are without having to run your whole suite. I also find you almost always end up with deeply nested tests with a lot of shared state that is hard to keep track of. If you really want a sort of “grouping” you could define a different Neospec::Suite that does what you want.

Should you use neospec?

No. Not unless you’re making a game in Taylor and even then I’d suggest holding off until I’ve actually used it myself for that!

Check it out anyway!

I suggest giving it a look anyway, it’s a fun little project and should be pretty easy to wrap your head around if you wanna look at the code.

Github