This document describes my thoughts on busted. My opinion on the tool is an ever-evolving thing, so these are not set in stone. I am writing them here to encourage discussion, more than to convince anyone.
(This might have been removed on latest busted versions)
busted does this thing where it "tries to be smart" and use the "most appropiate" version of Lua available on your system. So if you have Lua & LuaJIT, it automatically choses LuaJIT. It does so by using some bash file which tries to detect LuaJIT, then Lua, etc. This feature has given me enough trouble to want to remove it completely, in case it has not been removed already.
-C
can be replaced with cd
. --repeat
can be replaced by a for
loop. Etc.
busted copies a lot of things from rspec, but this feature is missing: when the list of failed specs is presented, they are listed like this, in red:
rspec foo.rb:45
rspec bar.rb:133
The number after the colons is the line where each failing spec starts. You can copy and paste these lines into the command line
and re-execute each failing test super easily (you don't have to edit them to "tag them"). If you give a line number inside
a spec (instead of the first line), the whole spec is executed. If you give a line outside of an it
block, but inside a
describe
block, the whole describe
block is executed.
In order to replicate this in busted
, the file path parser would have to be able to deal with the :line at the end of a path,
and busted would have to store more "geographical information" about each of its blocks.
I am not talking about the global variables here. Busted creates a lot of global state. For instance, say
, the library
it uses for i18n, stores all the translations in a global space. I would like to change that. It is not particularly tricky.
- Overriding a Lua global just so you can use it in your library is a bit ... well, rude. Like a construction worker occupying the whole street with his crane, when he could have moved it some meters to the side.
- I think expect-based solutions are better than assert-based ones:
expect(result).to_equal(4)
instead ofassert.equal(result, 4)
assert
needs an extra global (spy
) for call expectations, whileexpect
- It reads a bit more like English
- With assert I never know whether the "expected value" is the first or the second.
I have not decided on the specific syntax that I would like for expect
. rspec does lots of "magic" with it; For instance,
it needs a space after the "to": expect(result) to_equal(4)
. I would definitively not want that kind of magic here. I think
I also don't want to use :
. Maybe with dots: expect(x).to.equal(4)
or without "to": expect(x).equals(4)
.
First of all, it bothers me that they are called before_each
& after_each
instead of just before
& after
.
Second, I don't think they are important enough to deserve an extra global variable (well, I think each library deserves
one and only one variable, which is the one you get when you require
them).
For the simple case, you can replace what they do by creating an "init" or "teardown" and calling them from each it
block:
describe('the numbers', function()
local a,b
local function init()
a,b = 1,2
end
it('adds numbers', function()
init()
expect(a+b).equals(3)
end)
it(...
init()
end)
end)
The complex case is when you have several nested describe
blocks . In this case, if the "grandparent" has a before
, and the
"parent" has another before, and the child has another, then the it
blocks inside the child should run all of them
in order. One could always call grandpa_init() & parent_init()
from child_init()
. Less things would happen "automatically",
but maybe that wouldn't be so bad.
If you want to get fancy, you can even redefine it
inside a block to run whatever you want before or after each test.
I see them, maybe, as an option when creating a describe
block. So maybe you could do something like this:
describe('my numbers', {
before = function()
a,b = 1,2
end,
function()
it('adds numbers', function()
expect(a+b).equals(3)
end)
end
})
I am not sure whether the extra complexity is worth the effort even for that.
I am not even sure we need an it
. We could consider the describe
blocks with expectations as tests. And the ones without
them as groups. This information however is available at a later stage, when you are already "executing the tests". But
I am not sure it is needed.
I am not sure wether they are worth at all. I don't use them, and don't know anyone who uses them. I would certainly not include them on version 1.0 of any test lib.
If the need arises, then not relying on global variables and global state for executing the tests is paramount. In some cases it is inevitable (i.e. tests attacking the same database, resetting them before each test). Then I think there must be a way to "mark" the blocks which cannot be run in parallel somehow. And the blocks with the same mark can not be run simultaneously.
This is the big one. There are several options, and each one has drawbacks.
First option I can think of is: your spec files return a function. The function accepts all the dependencies it needs as parameters:
-- mylib_spec.lua
return function(describe, expect)
local mylib = require 'mylib'
describe(...
expect(...
end
Main problem is that the syntax is not as terse as when you use global variables. For this it is imperative to reduce
the amount of "global variables" to the absolute minimum. If your users have to write more than two (describe, it, expect, before, after, spy
) you have already lost them.