JanetRossini.github.io

Lua, LSL, Blender, Python in Second Life


Project maintained by JanetRossini

Refactoring Tests Framework

Jul 13, 2025 • [designluatesting]


Let’s clean up this code and get the new Tests as clean, lean, and mean as we can.

Currently the tests framework includes the new scheme with the expect syntax, and the old scheme with the test_something syntax. And the code is less than lovely as you will soon see. I propose to make the code better and to remove all vestiges of the old scheme along the way.

I have two test files that need to be combined into one. Let’s start there. I’ll take the one I used to test describe and all that, and add in the tests for the expect from the other file. You can scan the file here but no real need to dig into it:

-- test-suite

local Tests = require('./tests')
local _ = Tests

function _:featureTestSuite()
   _:describe("Initial Test Suite", function()

      _:test("Equality Test", function()
         _:expect("Foo").is("Foo")
      end)

      _:test("Expected to Fail", function()
         _:expect("Bar").is("Foo")
      end)
   end)
end

function _:featureST()
   local count = 0
   _:describe("Setup / Teardown", function()
      _:setup(function()
         count = 0
      end)
      _:teardown(function()
         count = 0
      end)

      _:test("Count 3", function()
         _:expect(count).is(0)
         count += 3
         _:expect(count).is(3)
      end)

      _:test("Count 4", function()
         _:expect(count).is(0)
         count += 4
         _:expect(count).is(4)
      end)
   end)
end



function Tests:test_simple_pass()
    self:assert_equals(2 + 2, 4, "should pass")
end

function Tests:test_expect_is()
   result = 54
   self:expect(result).is(54)
end

function Tests:ignore_test_expect_is_fail()
   result = 54
   self:expect(result).is(54)
end

function Tests:test_close_enough()
   result = 55
   self:expect(result).is(54,1)
end

function Tests:test_table_is()
   result = {1, 2, 3}
   expected = {1, 2, 3}
   self:expect(result).is(expected)
end

function Tests:test_isnt()
   unexpected = 100
   result = 99
   self:expect(result, "better not be 100").isnt(unexpected)
end

function Tests:test_isnt_epsilon()
   unexpected = 100
   result = 97
   self:expect(result, "stay away from 100").isnt(unexpected, 2)
end

function Tests:test_has()
   result = {1, 2, 3, 4}
   self:expect(result, "checking table").has(3)
end

function Tests:test_throw()
   local q = function()
      quaternion(1,2,3,4)
   end
   self:expect(q).throws("attempt to call a nil value")
end

Tests:execute()
Tests:run_tests()

As you can see here at the bottom, I’m calling run_tests, because the tests for the expect feature are using the old format of test. Let’s fix that first. I need to embed that blob of tests in a test suite function and a describe, like this:

function _:featureExpect()

   _:describe("Expect", function()
      -- all that stuff
   end)
end

Everything still runs, because run_tests is finding the test_methods. The Expect feature site is found and reports no tests as yet. All good.

Now convert one of the tests, this one:

function Tests:test_expect_is()
   result = 54
   self:expect(result).is(54)
end

To:

_:test("test_expect_is", function()
   result = 54
   _:expect(result).is(54)
end)

That moves one test into the output of the “Expect” suite, as intended. I can just about do that by rote, with multi-cursor edit the better to destroy the universe.

Change all the self:expect to _:expect. Easy. Run the tests just to see what happens. Runs just fine. Now change function Tests in a complex way best left to example. A bit of editing and all the tests in the Expect feature are converted.

I look forward to converting all the tests in my bezier file, but it should be straightforward and kind of a meditation, no real brain power required.

This is a good time to commit the code and I do so. I check it into GitHub as well. It won’t break my bezier code, yet.

Now we’ll begin by removing run_tests, the old form of, well, running the tests. All continues well. I think I’d like to look at how the various checkers, is and isnt and such, are implemented, to see if we can clean up this code a bit.

I’ve found a few things, but do not see a place to really start pulling at the snags, at least not this morning. We’ve accomplished a lot already and it is nearly time to cook the Sunday bacon.

I see a lot of code that is nearly the same …

if not self:tally(found) then
   local m = string.format('Table: %s did not contain %s',
      tostring(actual), tostring(expected))
   self:report(m, message)
end

if not self:tally(actual ~= expected) then 
   local m = string.format('Actual: %s Unexpected.', tostring(actual))
   self:report(m, message)
end

if not self:tally(ok) then
   local m = string.format('Actual: %s within %s of %s', 
      tostring(actual), tostring(epsilon), tostring(expected))
   self:report(m, message)
end

Something can be done about that, probably tomorrow, maybe this afternoon. And I don’t quite like the way the report method is used for both error reports and summary report. We’ll want to address that as well.

For now, a good result. We’ll push the code and article and go celebrate Sunday.

Safe paths!