本文简单介绍一下开发测试用例用到的几个关键接口。
1. 断言
在测试用例中少不了对结果进行校验,校验的方法一般称为断言(assert),也就是说,在进行一系列的操作之后,断定会出现某个确定性的结果,如果这个确定性的结果如期出现,则断言成功,被测试对象也就通过测试,否则就没通过测试。
本框架中的断言接口如下:
-- result:一个可以返回bool值的表达式,true表示测试通过
-- desc:附加描述,一般是对测试没通过的描述
function GBT_ASSERT(result, desc)
if result then return end -- 测试通过,不需要做什么
-- 下面几行代码主要是提取一些信息,以便给用户提供详细的结果信息,比如:
-- 测试用例名、用例所在lua文件名及行号,附加描述等。
local info = debug.getinfo(2, "Sl") -- 调用栈不包含本断言函数
local i, j = string.find(info.short_src, "%s*%-%s*%[%d+%]")
local script = info.short_src
if i and i > 1 then
script = string.sub(script, 1, i - 1)
end
-- 这里可以插入代码,将测试结果信息报告给用户界面
-- 开发人员可能会在一个用例里面下多个断言,而第一个失败的断言应该终止本用例的执行,所以这里调用
-- yield并以nil参数通知主线程,不用真正挂起该线程,而是直接释放掉该线程,继续执行后面的用例。
-- 可参见前文中Test_Node:ExecuteCase函数的实现。
coroutine.yield()
end
该接口的使用方法很简单,比如在一个MMORPG游戏中,开发一个简单的商城购买宝石的用例,该用例首先将角色的银两清空,然后购买宝石,最后断言购买不成功:
function GB_BuyDiamond_Money_NoEnough()
ClearMoney() -- 调用系统内部的函数,清空银两
BuyDiamond() -- 一系列的购买操作
local r = FindDiamondInPacket() -- 在包裹中查找购买到的宝石
GBT_ASSERT(r ~= true, "银两不足居然能买到宝石?")
end
2. 挂起
前文中有提到过,在某些情况下需要等待一段时间之后再进行其它操作或者校验,因此需要一个等待指定时间后恢复的接口:
-- 挂起用例,等待指定时间后恢复执行
-- ms:毫秒数
function GBT_WAIT(ms)
-- 这里以等待的毫秒数调用yield,在主线程中coroutine.resume(TC_Context.co)返回
-- 该毫秒数,然后以这个毫秒数启动定时器,在下一次迭代恢复本用例的执行,从而实现挂起
coroutine.yield(ms)
end
该接口的使用方法也很简单,仍以上文中的商城购买宝石为例,该用例首先将角色的银两数调整为一颗宝石的价格,然后购买宝石,最后断言购买成功,且银两数变为0:
function GB_BuyDiamond()
SetMoney(100) -- 假设商城中一颗宝石的价格是100银两
BuyDiamond() -- 一系列的购买操作
GBT_WAIT(2000) -- 等待两秒钟
local r = FindDiamondInPacket() -- 在包裹中查找购买到的宝石
GBT_ASSERT(r == true, "银两足够为什么买不到宝石?")
r = GetMoney()
GBT_ASSERT(r == 0, "购买宝石后银两扣除不正确!")
end
3. 延时
这个接口,先列出代码:
-- ms:毫秒数
function GBT_DELAY(ms)
TC_Context:SetDelay(ms)
end
仍以上文中的购买宝石为例:
function GB_BuyDiamond()
SetMoney(100)
BuyDiamond()
GBT_WAIT(2000)
local r = FindDiamondInPacket()
GBT_ASSERT(r == true, "银两足够为什么买不到宝石?")
r = GetMoney()
GBT_ASSERT(r == 0, "购买宝石后银两扣除不正确!")
GBT_DELAY(1000) -- 本用例执行完毕,1秒后继续执行后面的用例
end
购买宝石测试通过,最后在离开该用例的时候设置了1秒延时,告诉框架,1秒之后再继续执行后面的用例。
其实,该接口并非必需,你仍然可以用GBT_WAIT(1000)
来代替。区别就是GBT_WAIT(1000)
会挂起当前用例,1秒之后恢复该用例继续执行(但马上就执行完),而GBT_DELAY(1000)
不会挂起用例,一般用在一个用例函数的最后一行。大家可能已经看出来了,也可以用这个接口来实现前面的购买宝石的挂起-恢复-校验的用例,只是需要将上文的用例一分为二(这两个用例需要一前一后顺序注册到框架中):
-- 用例1,仅做购买操作,不进行校验
function GB_BuyDiamond()
SetMoney(100)
BuyDiamond()
GBT_DELAY(2000) -- 本用例执行完,等待两秒钟,继续执行后面的用例
end
-- 用例2,仅做校验操作
function GB_BuyDiamond_Check()
local r = FindDiamondInPacket() -- 在包裹中查找购买到的宝石
GBT_ASSERT(r == true, "银两足够为什么买不到宝石?")
r = GetMoney()
GBT_ASSERT(r == 0, "购买宝石后银两扣除不正确!")
end
这种实现方法,因为没有挂起任何用例函数,所以不需要使用lua的协程,就能实现“挂起等待恢复”的机制(当然,本框架实际上使用了lua协程机制)。至于用哪一种方法实现“挂起等待恢复”机制,看个人喜好了。
4. 结语
到目前为止,整个框架介绍的差不多了,你可以用本系列中介绍的方法手撸出一个简单的测试框架,并且可以开始撰写大量的测试用例了。
本框架的核心代码不超过1000行;但是,测试框架本身的代码虽然能跑起来,但并不具备可用性!你还是需要在一个支持树形控件的UI框架上,开发出几个界面来:
开发这些UI界面的代码量差不多在1000行以上了。本人开发过3个版本的UI界面,分别基于MFC、unity3D和CEGUI(CEGUI没有合用的树形控件,本人曾在CEGUI中手撸了一个树形控件),代码量都在1500左右。限于篇幅,且UI部分依赖于具体产品所使用的UI框架,就不展开讨论了。