本文首发于泊浮目的专栏:https://segmentfault.com/blog…
背景
最近项目在测试阶段陆陆续续的测出了一些 bug. 这个情况刚出现的时候, 让笔者很困惑——平时我们的每个 feature 代码都是跟随着大量看起来考虑很周全的 case 进入代码仓库的, 然而事实还是打了我们的脸. 故在本文, 笔者将会从最近的所学所想来谈谈编写测试的时候我们应该注意什么.
AIR 原则与 BCDE 原则
前阵子看了一本书, 里面提到了单元测试的一些原则:
宏观上, 单元测试要符合 AIR 原则
微观上, 单元测试的代码层面要符合 BCDE 原则
AIR 原则
AIR 即空气, 单元测试亦是如此。当业务代码在线上运行时, 可能感觉不到测试用例的存在和价值, 但在代码质量的保障上, 却是非常关键的。新增代码应该同步增加测试用例, 修改代码逻辑时也应该同步保证测试用例成功执行。AIR 原则具体包括:
A: Automatic (自动化)
I: Independent (独立性)
R: Repeatable (可重复)
简单的解释一下三个原则:
单元测试应该是全自动执行的。测试用例通常会被频繁地触发执行, 执行过程必须完全自动化才有意义。
如果单元测试的输出结果需要人工介入检查, 那么它一定是不合格的。单元测试中不允许使用 System.out 等方法来进行人工验证, 而必须使用断言来验证。
为了保证单元测试稳定可靠且便于维护,需要保证其独立性。用例之间不允许互相调用, 也不允许出现执行次序的先后依赖。
BCDE 原则
编写单元测试用例时, 为了保证被测模块的交付质量, 需要符合 BCDE 原则。
B: Border, 边界值测试, 包括循环边界、特殊取值、特殊时间点、数据顺序等。
C: Correct, 正确的输入, 并得到预期的结果。
D: Design, 与设计文档相结合, 来编写单元测试。
E: Error, 单元测试的目标是证明程序有错, 而不是程序无错。为了发现代码中潜在的错误, 我们需要在编写测试用例时有一些强制的错误输入 (如非法数据、异常流程、非业务允许输入等) 来得到预期的错误结果。
在 ZStack 白盒集成测试中实践原则
之前提到的原则是基于单元测试的, 但在 ZStack 的白盒测试中也可以作为有价值的参考.
戳此了解 ZStack 的白盒集成测试:https://segmentfault.com/a/11…
由于 ZStack 的整套测试框架也是基于 Junit 扩展而来, 因此也是一定程度上遵循了上面提到的 AIR 原则. 除了 A 原则,I 和 R 原则在一定程度上打了折扣:
I: 如果上一个测试没有清理干净状态, 则会影响下一个测试
R: 基于上面提到的 I, 很有可能导致可重复性大打折扣
当然, 出现这些问题时则表示当前的代码中有 bug. 但单元测试则不会受到这样的影响——它能测出 bug,AIR 原则也得以保证.
在本次示例中, 我们将以 VmInstance 的创建 API 即——APICreateVmInstacneMsg 作为测试对象. 如果读者不是很了解上下文, 也可以简单的看一下这个 Case:OneVmBasicLifeCycleCase
Border Test && Error Test
边界测试是用来探测和验证代码在处理极端的情况下会发生什么. 而错误测试为了保证 ZStack 在一些错误的状态下做出我们所期待的行为.
那么我们该如何编写这样的测试呢? 我们先来简单的理一下创建 Vm 的流程:
VmImageSelectBackupStorageFlow
VmAllocateHostFlow
VmAllocatePrimaryStorageFlow
VmAllocateVolumeFlow
VmAllocateNicFlow
VmInstantiateResourcePreFlow
VmCreateOnHypervisorFlow
VmInstantiateResourcePostFlow
而其中每一个步骤可以分成好几个小步骤, 以 VmAllocateHostFlow 为例:
我们可以看到, 根据不同的策略,allocateHost 里还会有好几个 flow. 而由于松耦合架构, 我们可以在测试中轻易的模拟极端问题的出现, 如:
找不到合适的 BackupStorage
HostCapacity 的不够
Agent 返回的回复在某一个时刻与管理节点的状态不同
…….
以此类推, 以上创建 vm 的 8 个 flow 都可以轻易模拟各种边界条件及错误情况.
Correct Test && Design Test
正确性测试听起来应该会很简单,(比如调用一个 API, 然后看结果返回是否正确)但如果放到集成测试中, 我们还是可以拓展出一些额外的关注点的. 还是以上面提到的 createVm 为例子, 我们看到了 8 个 flow, 然后里面可能还嵌套着好几个子 flow. 如图所示:
在编写正确性测试时, 我们可以考虑额外关注以下几点:
APIParam 在各个 Flow 间中转时是否如预期
关注管理节点内的服务:
Flow 之间调用的时序是否符合预期
Flow 之间流转时, 业务目标状态是否符合预期
关注管理节点外的服务:
对于 agent 的请求是否符合预期
在 API 调用完后, 相关资源的目标状态是否符合预期
而与文档结合的测试用例, 则应当由团队的测试人员来定义. 可以确定的是, 这类的测试更加关注于 API(即输入输出), 而不是内部的状态.