单元测试 |
|
Lua 有几个框架可以做到这一点
它们的功能集基本相同,包括:
商业 Lua 测试工具可从 Software Verification 获取[2]。
比较:[3]
Lua 本身的测试(用于测试 LuaImplementations)
由于我对 luaunit 更熟悉,我将提供一个 luaunit 的示例。这个例子很容易理解。
-- Some super function to test function my_super_function( arg1, arg2 ) return arg1 + arg2 end -- Unit testing starts require('luaunit') TestMyStuff = {} --class function TestMyStuff:testWithNumbers() a = 1 b = 2 result = my_super_function( a, b ) assertEquals( type(result), 'number' ) assertEquals( result, 3 ) end function TestMyStuff:testWithRealNumbers() a = 1.1 b = 2.2 result = my_super_function( a, b ) assertEquals( type(result), 'number' ) -- I would like the result to be always rounded to an integer -- but it won't work with my simple implementation -- thus, the test will fail assertEquals( result, 3 ) end -- class TestMyStuff LuaUnit:run()
运行它时,你会得到
shell $ lua use_luaunit1.lua Started on 12/20/14 14:37:50 >>>>>>>>> TestMyStuff >>> TestMyStuff.testWithNumbers >>> TestMyStuff.testWithRealNumbers test.lua:24: expected: 3, actual: 3.3 Failed ========================================================= Failed tests: ------------- >>> TestMyStuff.testWithRealNumbers failed test.lua:24: expected: 3, actual: 3.3 Success: 50% - 1 / 2, executed in 0.015 seconds
测试框架会报告导致错误的文件和行号,以及一些额外的信息。
当你的程序越来越大时,你需要越来越多的测试用例。下一个例子稍微复杂一些。
-- Some super function to test function my_super_function( arg1, arg2 ) return arg1 + arg2 end function my_bad_function( arg1, arg2 ) return arg1 - arg2 end -- Unit testing starts require('luaunit') -- now, we perform all the tests on int in one test class -- and the tests on float in another one -- when your test grows, you will have many test classes TestWithInt = {} --class function TestWithInt:setUp() -- this function is run before each test, so that multiple -- tests can share initialisations self.a = 1 self.b = 2 end function TestWithInt:tearDown() -- this function is executed after each test -- here, we have nothing to do so we could have avoid -- declaring it end function TestWithInt:testSuperFunction() result = my_super_function( self.a, self.b ) assertEquals( type(result), 'number' ) assertEquals( result, 3 ) end function TestWithInt:testBadFunction() result = my_bad_function( self.a, self.b ) assertEquals( type(result), 'number' ) assertEquals( result, -1 ) end function TestWithInt:testThatFails() -- you can test anything with assertEquals assertEquals( self.a, 1 ) assertEquals( type(self.a), 'number' ) -- will fail assertEquals( 'hop', 'bof' ) end -- class TestWithInt TestWithFloat = {} --class function TestWithFloat:setUp() -- this function is run before each test, so that multiple -- tests can share initialisations self.a = 1.1 self.b = 2.1 end function TestWithFloat:tearDown() -- this function is executed after each test -- here, we have nothing to do so we could have avoid -- declaring it end function TestWithFloat:testSuperFunction() result = my_super_function( self.a, self.b ) assertEquals( type(result), 'number' ) -- will fail assertEquals( result, 3 ) end function TestWithFloat:testBadFunction() result = my_bad_function( self.a, self.b ) assertEquals( type(result), 'number' ) -- will work, but only by chance :-) assertEquals( result, -1 ) end -- class TestWithFloat LuaUnit:run()
运行它
shell $ lua use_luaunit2.lua >>>>>> TestWithFloat >>> TestWithFloat:testSuperFunction use_luaunit2.lua:66: expected: 3.2, actual: 3 Failed >>> TestWithFloat:testBadFunction Ok >>>>>> TestWithInt >>> TestWithInt:testSuperFunction Ok >>> TestWithInt:testBadFunction Ok >>> TestWithInt:testThatFails use_luaunit2.lua:44: expected: 'hop', actual: 'bof' Failed Success : 60% - 3 / 5
你也可以单独运行测试
shell $ lua use_luaunit2.lua TestWithInt:testSuperFunction >>>>>> TestWithInt >>> TestWithInt:testSuperFunction Ok Success : 100% - 1 / 1 shell $ lua use_luaunit2.lua TestWithFloat >>>>>> TestWithFloat >>> TestWithFloat:testSuperFunction use_luaunit2.lua:66: expected: 3.2, actual: 3 Failed >>> TestWithFloat:testBadFunction Ok Success : 50% - 1 / 2
Shake 是一个简单透明的 Lua 测试引擎,它假设测试只使用标准的 assert 和 print 调用。如果你正在寻找 xUnit 风格的框架,请查看 lunit 和 luaunit。
许多 Lua 模块和应用程序使用简单的模式进行内部测试。它们有一个脚本(通常称为 test.lua),使用 API 调用来测试模块 API,并使用 assert() 调用来验证结果。在这些模块中,使用 print() 输出有关测试进度的信息,并使用 Lua 注释来告知阅读源代码的人正在测试什么,也是常见的做法。尽管这种方法很普遍,但对于那些想要测试多个模块或想要更详细地查看结果的人来说,它可能过于粗糙。
Shake 假设测试已经为上述场景实现了,但它提供了一个透明的测试引擎,供那些想要以批处理模式执行测试或想要获得各种结果概述的人使用。Shake 引擎不仅可以告知一组测试脚本中发现的测试、失败和错误的数量,还可以使用与 assert() 调用相关的输出和注释来推断有关测试上下文的信息。
Shake 的主要特点是透明性,这意味着模块作者和测试编写者不需要知道测试将使用 Shake 运行。只要测试调用 assert(),Shake 就可以从源代码和运行时执行中获取大量信息。这是通过使用 Leg(以及 LPeg)对测试源代码进行预处理来完成的,将每个对 assert() 的调用替换为可以提取断言中涉及的表达式、值和运算符信息的调用。
假设你安装了像 LuaFileSystem 这样的模块,并且你进入它的 /tests 目录并从那里运行 Shake,输出将是
>>>>~/workspace/luafilesystem/tests$ shake -> test.lua OK! _________________ Tests: 27 Failures: 0 Errors: 0
另一方面,如果你有一个像下面这样的测试脚本,它包含两个应该失败的断言(行号已标出)
1 items = 10 2 -- checks the correct case 3 assert (items == 10, "this should not fail") 4 5 items = 20 6 -- checks an overflow case 7 assert (items == 10, "wrong number of items") 8 9 print("Verifying the total") 10 items = 10 11 total = 30 12 assert (items == total, "wrong total")
Shake 会记录失败,但会运行整个测试脚本,并在最后报告
:~/workspace$ shake ---------------- test.lua failed! ---------------- -- checks an overflow case #7 assert (items == 10, "wrong number of items") items -> 20 Verifying the total #12 assert (items == total, "wrong total") items -> 10 total -> 30 _________________ Tests: 3 Failures: 2 Errors: 0
注意,与使用 Lua 运行测试脚本的默认输出相比,这要信息丰富得多
:~/workspace$ lua5.1 test.lua lua5.1: test.lua:7: wrong number of items stack traceback: [C]: in function 'assert' test.lua:7: in main chunk [C]: ?