I agree with MrFariator. However, using asserts is still a good idea, even with unit tests in place. Whenever you expect a function to only receive certain kinds or ranges of parameters, you can insert an assert() to verify that. I don't use asserts on the results of functions, only on the inputs of either functions or smaller blocks of code.
But in Lua you must use them carefully, because unlike in other languages where it's a special construct, assert() is a regular function. The worst danger performance wise is in the error message actually:
You can add this at the top of main.lua when you consider that the version is sufficiently tested: function assert() end; however that won't help if the performance sink is in the preparation of parameters. The best option is to make assert a macro and use a preprocessor:
- that way the whole call can be removed in the release version, including the parameters.
And back to unit tests, as for writing them, there are fancy tools such as Busted, but I just create a Lua file with asserts in it. Here's the unit test file for my WIP linear algebra library, to serve as example:
Code: Select all
local alg = require 'alg'
local function within(v1, v2, tol)
return math.abs(v1 - v2) <= tol
end
local m1 = alg.new_mat4()
local m2 = alg.new_mat4{2,3,5,7, 11,13,17,19, 23,29,31,37, 41,43,47,53}
local v1 = alg.new_vec4()
local v2 = alg.new_vec4{59,61,67,71}
local v3
-- Test eq, as we rely on it
m1[1] = m2[1] + 1
for i = 1, 2 do
alg.set_vec2(m1, m2)
assert(alg.eq_vec2(m1, m2))
assert(alg.eq_vec2(m2, m1))
m1[i] = m1[i] + 0.0000001
assert(not alg.eq_vec2(m1, m2))
assert(not alg.eq_vec2(m2, m1))
m1[i] = math.floor(m1[i])
assert(alg.eq_vec2(m1, m2))
assert(alg.eq_vec2(m2, m1))
end
m1[1] = m2[1] + 1
for i = 1, 3 do
alg.set_vec3(m1, m2)
assert(alg.eq_vec3(m1, m2))
assert(alg.eq_vec3(m2, m1))
m1[i] = m1[i] + 0.0000001
assert(not alg.eq_vec3(m1, m2))
assert(not alg.eq_vec3(m2, m1))
m1[i] = math.floor(m1[i])
assert(alg.eq_vec3(m1, m2))
assert(alg.eq_vec3(m2, m1))
end
m1[1] = m2[1] + 1
for i = 1, 4 do
alg.set_vec4(m1, m2)
assert(alg.eq_vec4(m1, m2))
assert(alg.eq_vec4(m2, m1))
m1[i] = m1[i] + 0.0000001
assert(not alg.eq_vec4(m1, m2))
assert(not alg.eq_vec4(m2, m1))
m1[i] = math.floor(m1[i])
assert(alg.eq_vec4(m1, m2))
assert(alg.eq_vec4(m2, m1))
end
assert(alg.eq_mat2 == alg.eq_vec4)
assert(alg.set_mat2 == alg.set_vec4)
m1[1] = m2[1] + 1
for i = 1, 3*3 do
alg.set_mat3(m1, m2)
assert(alg.eq_mat3(m1, m2))
assert(alg.eq_mat3(m2, m1))
m1[i] = m1[i] + 0.0000001
assert(not alg.eq_mat3(m1, m2))
assert(not alg.eq_mat3(m2, m1))
m1[i] = math.floor(m1[i])
assert(alg.eq_mat3(m1, m2))
assert(alg.eq_mat3(m2, m1))
end
m1[1] = m2[1] + 1
for i = 1, 4*4 do
alg.set_mat4(m1, m2)
assert(alg.eq_mat4(m1, m2))
assert(alg.eq_mat4(m2, m1))
m1[i] = m1[i] + 0.0000001
assert(not alg.eq_mat4(m1, m2))
assert(not alg.eq_mat4(m2, m1))
m1[i] = math.floor(m1[i])
assert(alg.eq_mat4(m1, m2))
assert(alg.eq_mat4(m2, m1))
end
-- vec2/mat2 tests
-- Expected values were calculated with iSymPy and Octave
alg.mul_mat2mat2(m1, alg.id_mat2, m2)
assert(alg.eq_mat2(m1, {2,3,5,7}))
alg.mul_mat2mat2(m1, m1, {11,13,17,19})
assert(alg.eq_mat2(m1, {73,83,174,198}))
alg.inplace_transpose_mat2(m1)
assert(alg.eq_mat2(m1, {73,174,83,198}))
alg.set_vec2(v2, {23,29,31,37})
alg.mul_vec2mat2(v1, v2, m1)
assert(alg.eq_vec2(v1, {4086,9744}))
alg.neg_vec2(v1, v1)
assert(alg.eq_vec2(v1, {-4086,-9744}))
alg.clear_vec2(v1)
assert(alg.eq_vec2(v1, {0,0}))
alg.mul_mat2vec2(v1, m1, v2)
assert(alg.eq_vec2(v1, {6725,7651}))
assert(alg.dot_vec2(v1, v2) == 376554)
assert(alg.cross_vec2(v1, v2) == 19052)
alg.add_vec2(v1, v2, {5,7})
assert(alg.eq_vec2(v1, {28,36}))
alg.mul_vec2vec2(v1, v2, {5,7})
assert(alg.eq_vec2(v1, {115,203}))
alg.scale_vec2(v1, v2, 2)
alg.set_vec2(v2, {46,58})
assert(alg.eq_vec2(v1, v2))
alg.set_vec2(v1, {0.5490196078431373, 0.34509803921568627})
alg.set_vec2(v2, {0.8156862745098039, 0.19215686274509805})
alg.composite_vec2vec2(v1, v1, v2)
assert(within(v1[1], 0.62027759475928960, 1e-13)
and within(v1[2], 0.47094194540561324, 1e-13))
alg.scale_mat2(m1, m1, 3)
assert(alg.eq_mat2(m1, {219,522,249,594}))
alg.add_mat2(m1, m1, {1,2,3,4})
assert(alg.eq_mat2(m1, {220,524,252,598}))
alg.rot_mat2(m1, m1, math.pi/2)
assert(within(m1[1], -252, 1e-13)
and within(m1[2], -598, 1e-13)
and within(m1[3], 220, 1e-13)
and within(m1[4], 524, 1e-13))
alg.seti_mat2(m1)
assert(alg.eq_mat2(m1, alg.id_mat2))
-- TODO
-- seti_mat2
-- rot_mat2
-- det_mat2
-- invert_mat2
-- vec3/mat3 tests
m1 = alg.new_mat3()
m2 = alg.new_mat3({2,3,5,7,11,13,17,19,23})
alg.mul_mat3mat3(m1, m2, {29,31,37,41,43,47,53,59,61})
assert(alg.eq_mat3(m1, {446,486,520,1343,1457,1569,2491,2701,2925}))
alg.inplace_transpose_mat3(m1)
assert(alg.eq_mat3(m1, {446,1343,2491,486,1457,2701,520,1569,2925}))
v1 = alg.new_vec3(13, 29, 41)
assert(alg.eq_vec3(v1, {13, 29, 41}))
alg.neg_vec3(v1, v1)
assert(alg.eq_vec3(v1, {-13, -29, -41}))
alg.clear_vec3(v1)
assert(alg.eq_vec3(v1, {0,0,0}))
alg.set_vec3(v1, {13, 29, 41})
v2 = alg.new_vec3(5, 61, 97)
assert(alg.dot_vec3(v1, v2) == 5811)
alg.cross_vec3(v1, v1, v2)
assert(alg.eq_vec3(v1, {312, -1056, 648}))
alg.scale_vec3(v1, v1, 0.125)
assert(alg.eq_vec3(v1, {39, -132, 81}))
alg.add_vec3(v1, v1, v2)
assert(alg.eq_vec3(v1, {44, -71, 178}))
alg.mul_vec3vec3(v1, v1, v2)
assert(alg.eq_vec3(v1, {44*5, 61*-71, 97*178}))
alg.cross_vec3(v1, alg.y_vec3, alg.x_vec3)
alg.neg_vec3(v2, alg.z_vec3)
assert(alg.eq_vec3(v1, v2))
-- vec4 tests
v1 = alg.new_vec4()
v2 = alg.new_vec4()
v3 = alg.new_vec4()
alg.set_vec4(v1, {0, 0, 0.5490196078431373, 0.34509803921568627})
alg.set_vec4(v2, {0, 0, 0.8156862745098039, 0.19215686274509805})
alg.composite_vec4vec4(v3, v1, v2)
assert(within(v3[3], 0.62027759475928960, 1e-13)
and within(v3[4], 0.47094194540561324, 1e-13)
and v3[1] == 0 and v3[2] == 0)
v1[2] = v1[3]; v1[3] = 0
v2[2] = v2[3]; v2[3] = 0
alg.composite_vec4vec4(v3, v1, v2)
assert(within(v3[2], 0.62027759475928960, 1e-13)
and within(v3[4], 0.47094194540561324, 1e-13)
and v3[1] == 0 and v3[3] == 0)
v1[1] = v1[2]; v1[2] = 0
v2[1] = v2[2]; v2[2] = 0
alg.composite_vec4vec4(v1, v1, v2)
assert(within(v1[1], 0.62027759475928960, 1e-13)
and within(v1[4], 0.47094194540561324, 1e-13)
and v1[2] == 0 and v1[3] == 0)
alg.clear_vec4(v1)
assert(v1[1] == 0 and v1[2] == 0 and v1[3] == 0 and v1[4] == 0)
v1 = alg.new_vec4({-2, 5, 7, -9})
alg.neg_vec4(v1, v1)
assert(v1[1] == 2 and v1[2] == -5 and v1[3] == -7 and v1[4] == 9)
alg.set_vec4(v2, {13, 19, 23, 29})
alg.mul_vec4vec4(v3, v1, v2)
assert(alg.eq_vec4(v3, {2*13,-5*19,-7*23,9*29}))
assert(alg.dot_vec4(v1, v2) == v3[1]+v3[2]+v3[3]+v3[4])
alg.add_vec4(v3, v1, v2)
assert(alg.eq_vec4(v3, {15,14,16,38}))
alg.scale_vec4(v1, v1, 2)
assert(alg.eq_vec4(v1, {4, -10, -14, 18}))
-- mat4 tests
m1 = alg.new_mat4()
m2 = alg.new_mat4{59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131}
alg.mul_mat4mat4(m1, {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53}, m1)
alg.mul_mat4mat4(m1, m1, m2)
assert(alg.eq_mat4(m1, {1585,1655,1787,1861,5318,5562,5980,6246,
10514,11006,11840,12378,15894,16634,17888,18710}))
alg.inplace_transpose_mat4(m1)
alg.set_mat4(m2, {1585,5318,10514,15894,1655,5562,11006,16634,
1787,5980,11840,17888,1861,6246,12378,18710})
assert(alg.eq_mat4(m1, m2))
-- Invert in place
alg.invert_mat4(m1, m1)
assert(alg.eq_mat4(m1, {2371/19404, -4601/19404, 5/63, 151/4851,
3191/129360, 12347/129360, -2/105, -2833/32340,
-877/4410, 3149/17640, 59/630, -1399/17640,
8629/77616, -9809/77616, -4/63, 3053/38808}))
m1, m2, v1, v2, v3 = nil
print("All passed")
I've caught a few typos with it. Unfortunately, writing them took long enough as to give up on continuing with the library, which supports what MrFariator said. Writing good tests is about knowing how the functions may fail, and try to write worst cases. I even
while working on the unit tests for a standard C library I was writing. But they are also time-consuming.