Why should you care?
- You’re tired of BDD-style test runners in Lua for your projects.
- You want a fast, no nonsense test runner.
The internet loves an unfair benchmark, so here goes. I converted the test suite for my side project, atlas, from busted to luatest. The execution time of the suite’s 109 tests went from an average of 1.326 seconds to 0.084 seconds! That’s not a misplaced zero! 1
luatest is roughly 15.8 times faster than busted.
The test suite went from feeling fast to feeling instant.
Intrigued? Get started with:
luarocks install luatest
You can also learn more about luatest on the GitHub repo page.
Compared to pytest
luatest is inspired by pytest, but it is missing some of pytest’s headline features. luatest does not include:
assertrewriting - pytest is awesome because most of the API consists of taking a comparison and throwing
assertat the beginning. luatest doesn’t have that, but does have the excellent
luassertlibrary for all your assertion needs.
- fixtures - pytest’s popular fixture feature is not present. Lua lacks the native reflection abilities to make the parameter-style insertion into test functions possible.
- plugins - At this time, there is no formalized plugin mechanisms to hook into the test lifecycle phases. This limits luatest’s current extensiblity.
So what does luatest bring to the table? Aside from a lightning quick runtime, luatest uses the same style of test identification using a double colon. This enables the test filtering that you may know and love from pytest. A filtered test run could look like:
I think these test identifiers are a massively underrated feature of pytest and I hope that using them in luatest will provide similar benefits. Having a clear and unique identifier provides the ability to filter tests for some tricky situations.
Have you ever tried to fix some test pollution between multiple tests and need to bisect the problem until you find the smallest set of tests that can reproduce an issue? These test identifiers make that problem much more tractable.
By following this style of identifiers, integrating luatest into an editor’s test execution tooling should be very doable. I’ve already done this myself for vim-test. You can checkout my vim-test luatest plugin (which I will try to get upstreamed to vim-test some day).
Compared to busted
busted has a much longer history
and is full of features
that don’t exist in luatest.
busted is also a BDD-style test runner
it("should frobnicate", function()
-- test logic here
After using BDD-style runners for a long time, I’ve got a hot take on this.
🔥 Test suites that use nested blocks for tests are an anti-pattern and should be avoided. 🔥
Because knowing what you’re testing requires mentally stitching
it blocks together to get the full view
of what is happening in the test.
That’s not a problem on the toy example presented above,
but a full test suite with multiple levels of
is much harder to reason about.
The test descriptions lose spatial locality
by being split apart by multiple inter-related
You can get the full test description at runtime,
but that’s a very late time to see that.
This style also makes it harder to uniquely identify a test. And I say this as the person who implemented the test discovery for individual tests for vim-test for busted! It was painful to get that code right!
I don’t mean to dunk on busted completely, but I have definitely soured over the years on the style of automated test that busted and other runners use.
Something that busted and luatest share is built-in support
for LuaCov for measuring code coverage.
luatest includes a
that will provide an lcov-based coverage file
that can be processed by services like Codecov.
luatest is the test runner for Lua that I wanted to exist in the world. Now it exists.
The next steps for luatest are to get the runner put through its paces and surface what features are vital for a successful ecosystem. Give it a try for your Lua project and enjoy!
Check out all the details I’ve left out on GitHub.