单元测试是什么?
对代码的最小模块(通常是函数或方法)进行测试,以保证代码实现了预期效果的一种方法。
如何进行单元测试?
举个例子,假如我设计了一个函数计算两数之和的方法
func sum(a, b):
return a + b
那么我怎么确定这个方法是否达到了我的预期效果呢?
很简单,调用一次就行
var result = sum(6, 3)
if result == 9:
print("测试成功")
但是这样太麻烦了,每次测试一个方法就需要写这么多代码,还需要我们去看输出结果
幸运的是,gds虽然是一个年轻的语言,但依然为我们提供了用于测试的一个关键字 assert (断言)
断言其实就是立一个flag,做一个预判——我确定当我向这个方法传递这些参数时,它必定会返回这个结果。
具体怎么使用呢?
很简单
当有了断言以后,上面的测试代码可以简化成这样:
assert 9 == sum(6, 3)
这行代码的含义是:
我断定:当我向div函数传入6和3的时候,函数会返回2
那么这个时候gds引擎在读取到这行代码后,会先执行div(6,3)
执行完成后,会把div(6,3) 返回的结果与 2 进行比较,如果条件成立,代码继续向下执行,反之则会报一个AssertError的错误
看到这里,恭喜你
你已经成功学会了如何利用断言在gds进行代码的测试
但是上面的测试还不够充分
真正有效的单元测试要把所有的可能性都测一边,这样才能保证更低的代码出错率。
所以我又连续写了如下代码。
assert 2 == sum(6, 3)
assert 4 == sum(0, 4)
assert 6.3 == sum(4.2, 2.1)
assert -110 == sum(-120, 10)
…
这样,一个比较完整的单元测试就写好了。
好的,接下来进入正题
尝试在Godot中实现一个单元测试框架。
为什么要实现?
因为开发中,代码量很多,所以会有很多函数或方法需要我们编写单元测试用例,而单元测试框架的用处就是测试的自动化。
要在Godot中实现一个简单的单元测试框架,可以利用Godot的场景树特性和脚本语言的动态特性来组织我们的单元测试用例。
下面这个是例子

这,是一个场景树
这个场景树除根节点之外的所有节点附加的脚本中编写的方法都是专用于测试的方法
下面再来看一下其中的一个节点的内容:

这个脚本里的test_next_id方法就是一个单元测试的用例,它测试了我的IdUtil类的next_id方法。
也就是说,当整个测试框架启动的时候,这个test_next_id方法会被自动的执行,这样就完成了对IdUtil类的next_id方法的测试。
下面来看一下整个测试框架的核心,也就是TestMain节点的代码内容

上面的代码的功能是:
当我们运行当前场景,也就是点击这个图标的时候
Godot会自动执行_ready方法中的test_clildren方法,test_clildren是一个递归方法,它每次执行都会自动获取子节点并执行子节点的脚本中的方法,然后再去依次执行父节点中的方法,这样我们就实现了对一个场景树中的所有子节点的方法进行自动测试的功能。
这部分的代码的核心有几个核心点想要说一下:
gds的动态特性
大家可以看到这部分代码中,有三个方法,get_method_list、has_method和call,
这三个方法的作用分别是获取当前对象的方法列表、当前对象是否包含指定方法和执行当前对象的方法,它们也是整个单元测试框架的核心,他们是gds动态特性的一部分。通过这三个方法的组合,单元测试框架才有了自动获取对象的方法并执行的能力
限制测试用例的方法签名
在获取了代码的方法后,我们对方法名称和参数列表进行了限制,只有方法名以test_开头并且参数列表为空的方法,才会被测试。有两个好处:第一个好处就是方便框架和程序员对测试方法进行区分,不至于将普通方法和测试方法弄混;第二个就是参数为空也尽可能保证了测试用例的独立性和框架的易用性,否则每个测试用例都都需要往里传递不同的参数,测试用例的执行结果依赖外部参数,这与我们设计测试用例的目的相悖。
好了,暂时就写这么多,有什么问题大家一起交流