In this section, we’ll take a look at the CodeaUnit code and see what we can learn about how it works. Having written what’s far below, I feel the need to start with some code as our own.

Objects, functions, tables

You’ve seen in our use of CodeaUnit that we get to say things like

_:expect(button.pressed).is(true)

That syntax is weird enough that it made me wonder what was going on. _:expect() is the syntax for a method send to an object. So there must be an object around here somewhere, named _. What is it? (Quick answer based on reading a lot of code: it’s an instance of CodeaUnit class.) There must be a method in there, called expect. A method in Codea is a function which expects self as its first argument, self being the instance to which the message expect was sent.

What about the .is()? That is a function call (not a method call). Unlike a method call, a function call isn’t exactly sent to anyone, but the symbol “is” must be resolved somewhere.

It gets worse before it gets better. The notation X.is means, precisely, X[“is”]. It’s syntactic sugar, and nothing more. So X must be a table, because only tables understand []. More precisely, X[“is’] tells the Lua compiler to emit code that looks up “is” in what is presumed to be a table, which will have been resolved by the compiler by figuring out what X is.

In Lua, a class instance is a table. The table acts as a mapping from various symbols, like the member variables and functions, to their values. So if we have a class instance with a member variable X and a member variable Y, and a function sum(), it is represented by a table defining at least X, Y, and sum. Here’s an example:

Table = { X = 3, Y = 2, sum = function() 
    return Table.X + Table.Y
end}

function setup()
    print(Table.X, Table.Y, Table.sum())
end

This prints, I hope unsurprisingly, “3 2 5”. Let’s go a step further and build a tiny class:

Toy = class()

function Toy:expect(aValue)
    self.saved = aValue
    
    function is(anotherValue)
        if (anotherValue == self.saved ) then
            print("OK")
        else
            print("Not OK")
        end
    end
    
    return { is = is }
end

function setup()
    Toy:expect(3).is(5)
    Toy:expect(4).is(2+2)
end
```

This prints "Not OK" for the 3 is 5, then OK for the 4 is 2+2. Are you troubled by the return table, `{ is = is}`? It could be written this way: `return { ["is"] = is }` instead. The table returned has one element, key "is", value our "is" function. The `is` function is compiled into the class, and therefore understands self. So it can fetch self.saved and use it just fine. 

Note, however, that we are sending the message `expect` to Toy, the class, not an instance! That works because to build an instance,  Codea Lua just copies the class and returns it as an instance. A class in Lua is a prototype, not a factory.

However, to get an instance we can do this:

~~~lua
Toy = class()

function Toy:expect(aValue)
    self.saved = aValue
    
    function is(anotherValue)
        if (anotherValue == self.saved ) then
            print("OK")
        else
            print("Not OK")
        end
    end
    
    return { ["is"] = is }
end

_ = Toy()

function setup()
    _:expect(3).is(5)
    _:expect(4).is(2+2)
end

Note the _ = Toy(). That just defines the global variable named _ to be an instance of Toy, and now, voila! We have the syntax that CodeaUnit uses. Now let’s do one more thing. Our version above stores the saved value in the instance. We could instead make it local, like this:

Toy = class()

function Toy:expect(aValue)
    local saved = aValue
    
    function is(anotherValue)
        if (anotherValue == saved ) then
            print("OK")
        else
            print("Not OK")
        end
    end
    
    return { ["is"] = is }
end

_ = Toy()

function setup()
    _:expect(3).is(5)
    _:expect(4).is(2+2)
end

Here we just define a local value, saved, inside the expect function definition. That local is of course available everywhere inside expect and therefore inside is, which uses it to check for “correctness”. Now is is not tied to the Toy instance at all: everything is inside the function that expect defined, and is found by is in the table that expect returns.

Wow. To me, this is pretty intricate. The more comfortable you are with anonymous functions and their scoping, the more comfortable you’re likely to be with this. Quite possibly you should ignore the words here, and figure out the code for yourself. Then write your own words and share them: maybe they’ll be better than mine. Certainly they’ll be better for you!

Summing up, then, CodeaUnit defines a class, and saves an instance of the class in _. In its :expect() function, it defines various internal functions, like is, isnt, has, throws, before, after, with variables bound as needed, and returns from expect a table containing those definitions. Everything else is elaboration. Let’s look at that now.

CodeaUnit

Here’s the code:

CodeaUnit = class()

function CodeaUnit:describe(feature, allTests)
    self.tests = 0
    self.ignored = 0
    self.failures = 0
    self._before = function()
    end
    self._after = function()
    end

    print(string.format("Feature: %s", feature))

    allTests()

    local passed = self.tests - self.failures - self.ignored
    local summary = string.format("%d Passed, %d Ignored, %d Failed", passed, self.ignored, self.failures)

    print(summary)
end

function CodeaUnit:before(setup)
    self._before = setup
end

function CodeaUnit:after(teardown)
    self._after = teardown
end

function CodeaUnit:ignore(description, scenario)
    self.description = tostring(description or "")
    self.tests = self.tests + 1
    self.ignored = self.ignored + 1
    if CodeaUnit.detailed then
        print(string.format("%d: %s -- Ignored", self.tests, self.description))
    end
end

function CodeaUnit:test(description, scenario)
    self.description = tostring(description or "")
    self.tests = self.tests + 1
    self._before()
    local status, err = pcall(scenario)
    if err then
        self.failures = self.failures + 1
        print(string.format("%d: %s -- %s", self.tests, self.description, err))
    end
    self._after()
end

function CodeaUnit:expect(conditional)
    local message = string.format("%d: %s", (self.tests or 1), self.description)

    local passed = function()
        if CodeaUnit.detailed then
            print(string.format("%s -- OK", message))
        end
    end

    local failed = function()
        self.failures = self.failures + 1
        local actual = tostring(conditional)
        local expected = tostring(self.expected)
        print(string.format("%s -- Actual: %s, Expected: %s", message, actual, expected))
    end

    local notify = function(result)
        if result then
            passed()
        else
            failed()
        end
    end

    local is = function(expected)
        self.expected = expected
        notify(conditional == expected)
    end

    local isnt = function(expected)
        self.expected = expected
        notify(conditional ~= expected)
    end

    local has = function(expected)
        self.expected = expected
        local found = false
        for i,v in pairs(conditional) do
            if v == expected then
                found = true
            end
        end
        notify(found)
    end

    local throws = function(expected)
        self.expected = expected
        local status, error = pcall(conditional)
        if not error then
            conditional = "nothing thrown"
            notify(false)
        else
            notify(string.find(error, expected, 1, true))
        end
    end

    return {
        is = is,
        isnt = isnt,
        has = has,
        throws = throws
    }
end

CodeaUnit.execute = function()
    for i,v in pairs(listProjectTabs()) do
        local source = readProjectTab(v)
        for match in string.gmatch(source, "function%s-(test.-%(%))") do
            -- print("loading", match)
            loadstring(match)()
        end
    end
end

CodeaUnit.detailed = true



_ = CodeaUnit()

parameter.action("CodeaUnit Runner", function()
    CodeaUnit.execute()
end)

Let’s begin here:

CodeaUnit.execute = function()
    for i,v in pairs(listProjectTabs()) do
        local source = readProjectTab(v)
        for match in string.gmatch(source, "function%s-(test.-%(%))") do
            -- print("loading", match)
            loadstring(match)()
        end
    end
end

-- ...

parameter.action("CodeaUnit Runner", function()
    CodeaUnit.execute()
end)

The code just above creates a test button in the Codea parameters area. The button calls execute(). This starts the ball rolling. The execute function loops over all project tabs, and looks at the source code for all functions whose names start with “test”, and executes what it finds. What does it find? Well, here’s a trivial test example:

function testTrivial()
    CodeaUnit.detailed = true

    _:describe("Trivial Test", function()

        _:test("Try Doubling X", function()
            local x = 2
            _:expect(x*x).is(3)
        end)

    end)
end

CodeaUnit will find and execute testTrivial(). That function sets CodeaUnit.detailed to true and then calls _:describe, with the string parameter and a function definition, which in this case if a function which, if called would call _:test. How does that get called? Let’s look at CodeaUnit describe.

function CodeaUnit:describe(feature, allTests)
    self.tests = 0
    self.ignored = 0
    self.failures = 0
    self._before = function()
    end
    self._after = function()
    end

    print(string.format("Feature: %s", feature))

    allTests()

    local passed = self.tests - self.failures - self.ignored
    local summary = string.format("%d Passed, %d Ignored, %d Failed", passed, self.ignored, self.failures)

    print(summary)
end

The describe function expects a feature string, and an allTests parameter which is a function. We see from our example that the function may call _:test, and from other examples we know that it could call _:before or _:after. Anyway, describe sets up some counts, sets default empty before and after functions, prints its message and calls allTests, the function we provided including all our tests. When our allTests function returns, describe prints the summary information. What about our allTests? It just calls _:test, which looks like this:

function CodeaUnit:test(description, scenario)
    self.description = tostring(description or "")
    self.tests = self.tests + 1
    self._before()
    local status, err = pcall(scenario)
    if err then
        self.failures = self.failures + 1
        print(string.format("%d: %s -- %s", self.tests, self.description, err))
    end
    self._after()
end

Well, now we’re getting down to it. We tally the test, run the _before function, which is in our case the empty default but could have been provided, and calls the scenario function with pcall, which ensures that errors thrown inside will return rather than stop. Thus the if err can tally and print any failures. Then we do the _after, if any.

Remember that our scenario looks like this:

        _:test("Try Doubling X", function()
            local x = 2
            _:expect(x*x).is(3)
        end)

Therefore what’s going to happen is that we’re going to run our little function, which will set x and then call _expect, which looks like this:

function CodeaUnit:expect(conditional)
    local message = string.format("%d: %s", (self.tests or 1), self.description)

    local passed = function()
        if CodeaUnit.detailed then
            print(string.format("%s -- OK", message))
        end
    end

    local failed = function()
        self.failures = self.failures + 1
        local actual = tostring(conditional)
        local expected = tostring(self.expected)
        print(string.format("%s -- Actual: %s, Expected: %s", message, actual, expected))
    end

    local notify = function(result)
        if result then
            passed()
        else
            failed()
        end
    end

    local is = function(expected)
        self.expected = expected
        notify(conditional == expected)
    end

    local isnt = function(expected)
        self.expected = expected
        notify(conditional ~= expected)
    end

    local has = function(expected)
        self.expected = expected
        local found = false
        for i,v in pairs(conditional) do
            if v == expected then
                found = true
            end
        end
        notify(found)
    end

    local throws = function(expected)
        self.expected = expected
        local status, error = pcall(conditional)
        if not error then
            conditional = "nothing thrown"
            notify(false)
        else
            notify(string.find(error, expected, 1, true))
        end
    end

    return {
        is = is,
        isnt = isnt,
        has = has,
        throws = throws
    }
end

Wow. Well, in our case, conditional is the value of x*x, which I’d guess to be 4. Then, expect defines a whole raft of functions, including passed, failed, notify, is, isn’t, and so on, each stored in variables local to this execution of expect. We then return, from expect, a small table defining the strings “is”, “isnt”, “has”, “throws” as those functions. So next, when we call .is(3), we’ll execute our specially bound version of is that’s connected to our expected value, our notify, and so on.

Is this intricate? Yes, it bloody well is. But a testing framework like this generally is intricate, because each test has to be uniquely bound to its input values and expected values, which have to permeate through all the notify functions and so on. Anyway, we’re executing our little function that calls expect and then is. So our is now checks conditional == expected, which is resolved to notify(3 == 4) and since four isn’t three, we call our previously bound failed function, which tallies the failure and prints the bad news.