Skip to content

Instantly share code, notes, and snippets.

@kikito
Created February 6, 2016 23:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kikito/27651f3e8df6dd6b0958 to your computer and use it in GitHub Desktop.
Save kikito/27651f3e8df6dd6b0958 to your computer and use it in GitHub Desktop.
What I would change about busted

What I would change about busted

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.

1. Remove the "automatic Lua detection logic"

(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.

2. Unnecessary command-line options.

-C can be replaced with cd. --repeat can be replaced by a for loop. Etc.

3. Allow executing individual specs from the command line via a :line

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.

4. Global state

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.

5. assert

  • 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 of assert.equal(result, 4)
    • assert needs an extra global (spy) for call expectations, while expect
    • 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).

6. before_each, after_each

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.

7. It

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.

8. Multi-threaded tests with Lualanes

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.

9. Global variables

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment