Lua, by default, compares tables for identity, not contents equality. We would like to do better with Tests. When we’re done with this improvement, tests will diagnose incorrect, missing, and excess values in tables.
-- test passes, tables have same keys and values
function Tests:test_tables_equal()
result = {a=5, b="hello"}
expected = {b="hello", a=5}
self:assert_equals(result, expected)
end
Before my changes, it does not pass, saying:
test_tables_equal: table: 0x000000002066997c was expected to be table: 0x0000000020669954 ()
I propose to check at the top of assert_equals whether both result and expected are tables, and if they are, to check them key by key. It will be necessary to check them both ways, since either one might have keys not in the other.
I begin with this much:
function Tests:assert_equals(result, expected, message)
if type(result) == "table" and type(expected) == "table" then
self:assert_tables_equal(result, expected)
return
end
local message = message or ""
local correct = result == expected
if correct then
tests_passing += 1
else
test_failing += 1
self:diagnose(result, 'was expected to be', expected, message)
end
end
function Tests:assert_tables_equal(result, expected)
for k, v in pairs(expected) do
self:assert_equals(result[k], v)
end
for k, v in pairs(result) do
self:assert_equals(v, expected[k])
end
end
My test passes. Well done, Janet, recursion worked on the first try. Now we need to check at least three more things:
result
has same keys but different values than expected
;result
is missing a key that expected
has;result
has excess key that expected
does not have.For all of these, I think we’ll need to create better messages than the default.
I’ll do one case at a time, as it is pretty widely considered better to do many small tests than one big one.
Here’s the next test. I expect all these will fail as intended but the messages may be hard to understand.
-- test fails
function Tests:test_result_has_wrong_value()
result = {a=5, b="goodbye"}
expected = {b="hello", a=5}
self:assert_equals(result, expected)
end
Run tests. Two lines come out, no real surprise since we iterate twice. Maybe we can do better than that. Here’s the output:
test_result_has_wrong_value:
goodbye was expected to be hello ()
test_result_has_wrong_value:
goodbye was expected to be hello ()
I’d like the message to be something like this:
result key b -> goodbye, expected hello.
But the message as presently defined is appended to the default, it does not replace it. Here’s the best I can see to do:
test_result_has_wrong_value:
goodbye was expected to be hello (key: b)
That much is easy:
function Tests:assert_tables_equal(result, expected)
for k, v in pairs(expected) do
self:assert_equals(result[k], v, `key: {k}`)
end
for k, v in pairs(result) do
self:assert_equals(v, expected[k])
end
end
Let’s change assert_tables_equal
so that we don’t get duplicate messages, instead checking for excess keys only. Maybe like this:
function Tests:assert_tables_equal(result, expected)
for k, v in pairs(expected) do
self:assert_equals(result[k], v, `key: {k}`)
end
for k, v in pairs(result) do
if expected[k] == nil then
self:assert_equals(v, nil, "unexpected key")
end
end
end
That suppressed the extra messages when keys are preesent but do not have the same values. Now we need two more tests, one for insufficient keys and one for excess.
-- test fails
function Tests:test_result_has_excess_value()
result = {a=5, b="hello", surprise="oops"}
expected = {b="hello", a=5}
self:assert_equals(result, expected)
end
That one gives this message:
test_result_has_excess_value:
oops was expected to be nil (unexpected key)
I think the code already handles the missing value case well enough already, diagnosing a nil in result. Let’s find out:
-- test fails
function Tests:test_result_has_missing_value()
result = {a=5}
expected = {b="hello", a=5}
self:assert_equals(result, expected)
end
That fails with this:
test_result_has_missing_value:
nil was expected to be hello (key: b)
I think that will do. All the cases are pretty clearly delineated with the extra messages. I am confident that nested tables will work, and I’m not going to test them just now.
I think that the generic message “foo was expected to be bar” is a bit long, and the messages of course all come out prefixed by the object name, but we’ll let those niceties ride for now.
And we’ll leave all the samples in the framework so that users can see how it all works, and remove them when they write their own tests.
I’ll clean up the comments a bit before I save.
Saved and put into the files for download.
Woot! We can test tables for equality and get some pretty nice diagnostics when they’re wrong. I am pleased!