Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Alternate CodeaUnit

A modified version of @jakesankey’s extremely helpful CodeaUnit.

Somebody might find this useful, as it’s been tweaked to do a few convenient things:

  • The formatting of the results is modified to make it easier to scroll through when there are lots of tests happening at the same time.
  • The pass/fail status of any given assertion has been given greater emphasis to make it easier to spot failed tests while scrolling.
  • Multiple assertions inside a single test can each have their own labels which will be included in the printout.
  • Multiple assertions inside a single test are automatically numbered in the style “1a, 1b, 1c, etc” to make it easier to identify which test they belong to.

It’s been working for me, but I haven’t fully tested it (ironically), so there might still be a glitch here or there. If you find one please let me know.

--from an original by jakesankey CodeaUnit = class() CodeaUnit.isRunning = false CodeaUnit.doBeforeAndAfter = true function CodeaUnit:describe(feature, allTests) print(string.format("\t****\n\t%s\n\t****", feature)) if self.skip == true then print("\t * Tests Skipped") else self.tests = 0 self.subtests = 0 self.totalTests = 0 self.ignored = 0 self.failures = 0 self.message = "message not set" self.debugReporting = false self._before = function() end self._after = function() end allTests() local passed = self.totalTests - self.failures local summary = string.format("\t\t\t----------\n\t\t\tPass: %d\n\t\t\tIgnore: %d\n\t\t\tFail: %d", passed, self.ignored, self.failures) print(summary) end 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.tests = self.tests + 1 self.totalTests = self.totalTests + 1 self._before() local beforeString = "__________________\n*** First. CodeaUnit:test(...) description before assignment: "..tostring(self.description) self.description = tostring(description or "") local afterString = "*** CodeaUnit:test(...) description after assignment: "..tostring(self.description) if self.debugReporting then print(string.format("%s\n%s", beforeString, afterString)) end 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() if self.subtests ~= 0 then self.totalTests = self.totalTests + self.subtests - 1 end self.subtests = 0 self.description = nil self.message = nil end --function CodeaUnit:expect(conditional) --takes one or two arguments --can take just the expected value, or a name for this individual 'expect' call plus the expected value --this allows multiple 'expect' calls in a single test to all show different titles function CodeaUnit:expect(...) local encoding = "abcdefghijklmnopqrstuvwxyz" local function letterFromNum(i) return encoding:sub(i,i) end --detecting #args will mess up if expected value has returned nil, because nil isn't counted as a value local args = {...} if #args == 2 then multiTest = true end local unpackedArgs = tostring(table.unpack(args)) if self.debugReporting then local descriptionString = "*** Second. CodeaUnit:expect(...) self.description: "..tostring(self.description) local argsExplained = "*** CodeaUnit:expect(...) args: "..#args..", unpacked: "..unpackedArgs print(string.format("%s\n%s", descriptionString, argsExplained)) end self.message = string.format("%d. %s:", (self.tests or 1), self.description) if not multiTest then conditional = args[1] elseif #args == 2 then local premessage = "" self.subtests = self.subtests + 1 if self.subtests == 1 then premessage = string.format("%s\n\n", self.message) end conditional = args[2] self.message = string.format("%s %d.%s. %s", premessage, (self.tests or 1), letterFromNum(self.subtests), args[1]) end local passed = function() if CodeaUnit.detailed then print(string.format("%s\n Expected: %s\n -- OK", self.message, self.expected)) end end local failed = function() self.failures = self.failures + 1 local actual = tostring(conditional) local expected = tostring(self.expected) print(string.format("%s\n Expected: %s\n -- FAIL: found %s", self.message, expected, actual)) end local notify = function(result) if self.debugReporting then print("notify() message: "..tostring(self.message)..", self.expected: "..tostring(self.expected)) end 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 if not found then conditional = "no such value" 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() CodeaUnit.isRunning = true for i,v in pairs(listProjectTabs()) do local source = readProjectTab(v) for match in string.gmatch(source, "function%s-(test.-%(%))") do load(match)() end end end CodeaUnit.detailed = true _ = CodeaUnit() parameter.action("CodeaUnit Runner", function() CodeaUnit.execute() end)


  • edited November 11 Posts: 1,547

    And here’s an example of using it:

    function testEasyCraft() CodeaUnit.detailed = true CodeaUnit.skip = false _:describe("Testing EasyCraft", function() _:before(function() end) _:after(function() end) _:test("makeAThing() creates entity when called without parameters", function() local entity = EasyCraft.makeAThing() _:expect(tostring(entity)).is("entity") end) _:test("makeAThing() creates correct name, position, rotation, scale, and model", function() local name = "Joe Entity" local position = vec3(19, 22, 3.333) local rotation = vec3(-26.973995, -30.679983, -28.186922) local eScale = vec3(99, 5, 2) local modelPack = "Watercraft" local modelName = "watercraftPack_003_obj" local entity = EasyCraft.makeAThing(name, modelPack, modelName, position, rotation, eScale) local angleAdjuster = scene:entity() angleAdjuster.eulerAngles = vec3(-26.973995, -30.679983, -28.186922) local regurgutatedEulers = angleAdjuster.eulerAngles _:expect("name assignment", _:expect("position assignment", tostring(entity.position)).is(tostring(position)) _:expect("rotation assignment", tostring(entity.eulerAngles)).is(tostring(regurgutatedEulers)) _:expect("scale assignment", tostring(entity.scale)).is(tostring(eScale)) _:expect("modelName", entity.modelName).is(modelName) end) _:test("reading, loading, and calling a function saved to a tab works", function() local stringToReturn = "anotherMeaninglessString" saveProjectTab("meaninglessTab", [[ function meaninglessFunction() local anotherMeaninglessString = ']]..stringToReturn..[[' return anotherMeaninglessString end]]) local tab = readProjectTab("meaninglessTab") load(tab)() local result = meaninglessFunction() saveProjectTab("meaninglessTab", nil) --deletes _:expect(stringToReturn).is(result) end) end
  • edited November 11 Posts: 1,275

    Interesting, I'll check it out. I should probably publish the one I'm using.

    One thing I did differently is that I put the description of the test as the second, optional argument to "expect", which makes the code easier, as you can just default it rather than count the args etc.

    function CodeaUnit:expect(conditional, msg) local message = string.format("%d: %s %s", (self.tests or 1), self.description, (msg or "")) local passed = function() if CodeaUnit_Detailed then print(string.format("%s -- OK", message)) end end
  • Posts: 1,547

    @RonJeffries oooooo that looks sweet.

    All the CodeaUnit code makes my head spin, honestly, and it was really hard for me to get my changes to work at all, so I’m a bit scared to mess with it, but this seems like a really good idea.

    And it fits your general principle that if something is too complicated, you’re probably doing it the wrong way.

Sign In or Register to comment.