分布式程序的自动化回归测试
作者:网络转载 发布时间:[ 2011/5/17 13:53:51 ] 推荐标签:
*能比较方便地测试 failure 场景。比如,若要测试 DataNode 出错时 NameNode 的反应,只要让 test harness 模拟的那个 mock DataNode 返回我们想要的出错信息。要测试 NameNode 在某个 DataNode 失效之后的反应,只要让 test harness 断开对应的网络连接即可。要测量某请求超时的反应,只要让 Test harness 不返回结果即可。这对构建可靠的分布式系统尤为重要。
*帮助开发人员从使用者的角度理解程序,程序的哪些行为在外部是看得到的,哪些行为是看不到的。
*有了一套比较完整的 test cases 之后,甚至可以换种语言重写被测程序(假设为了提高内存利用率,换用 C++ 来重新实现 NameNode),测试用例依旧可用。这时 test harness 起到知识传承的作用。
*发现 bug 之后,往 test harness 里添加能复现 bug 的 test case,修复 bug 之后,test case 继续留在 harness 中,反正出现回归(regression)。
实现要点
*Test harness 的要点在于隔断被测程序与其他程序的联系,它冒充了全部其他程序。这样被测程序像被放到测试台上观察一样,让我们只关注它一个。
*Test harness 要能发起或接受多个 TCP 连接,可能需要用某个现成的 NIO 网络库,如果不想写成多线程程序的话。
*Test harness 可以与被测程序运行在同一台机器,也可以运行在两台机器上。在运行被测程序的时候,可能要用一个特殊的启动脚本把它依赖的 host:port 指向 test harness。
*Test harness 只需要表现得跟它要 mock 的程序一样,不需要真的去实现复杂的逻辑。比如 mock DataNode 只需要对 NameNode 返回“Yes sir, 数据已存好”,而不需要真的把数据存到硬盘上。若要 mock 比较复杂的逻辑,可以用“记录+回放”的方式,把预设的响应放到 test case 里回放(replay)给被测程序。
*因为通信走 TCP 协议,test harness 不一定要和被测程序用相同的语言,只要符合协议行。试想如果用共享内存实现 IPC,这是不可能的。陈硕在《在 muduo 中实现 protobuf 编解码器与消息分发器》中提到利用 protobuf 的跨语言特性,我们可以采用 Java 为 C++ 服务程序编写 test harness。其他跨语言的协议格式也行,比如 XML 或 Json。
*Test harness 运行起来之后,等待被测程序的连接,或者主动连接被测程序,或者兼而有之,取决于所用的通信方式。
*一切绪之后,Test harness 依次执行 test cases。一个 NameNode test case 的典型过程是:test harness 模仿 client 向被测 NameNode 发送一个请求(eg. 创建文件),NameNode 可能会联络 mock DataNode,test harness 模仿 DataNode 应有的响应,NameNode 收到 mock DataNode 的反馈之后发送响应给 client,这时 test harness 检查响应是否符合预期。
*Test harness 中的 test cases 以配置文件(每个 test case 有一个或多个文本配置文件,每个 test case 占一个目录)方式指定。test harness 和 test cases 连同程序代码一起用 version controlling 工具管理起来。这样能复现以外任何一个版本的应有行为。
*对于比较复杂的 test case,可以用嵌入式脚本语言来描述场景。如果 test harness 是 Java 写的,那么可以嵌入 Groovy,像陈硕在《“过家家”版的移动离线计费系统实现》中用 Groovy 实现计费逻辑一样。Groovy 调用 test harness 模拟多个程序分别发送多份数据并验证结果,groovy 本身是程序代码,可以有逻辑判断甚至循环。这种动静结合的做法在不增加 test harness 复杂度的情况下提供了相当高的灵活性。
*Test harness 可以有一个命令行界面,程序员输入“run 10”选择执行第 10 号 test case。
几个实例
Test harness 这种测试方法适合测试有状态的、与多个进程通信的分布式程序,除了 Hadoop 中的 NameNode 与 DataNode,我还能想到几个例子。
1. chat 聊天服务器
聊天服务器会与多个客户端打交道,我们可以用 test harness 模拟 5 个客户端,模拟用户上下线,发送消息等情况,自动检测聊天服务器的工作情况。
2. 连接服务器、登录服务器、逻辑服务器
这是云风在他的 blog 中提到的三种网游服务器(http://blog.codingnow.com/2007/02/user_authenticate.html,http://blog.codingnow.com/2006/04/iocp_kqueue_epoll.html,http://blog.codingnow.com/2010/11/go_prime.html),我这里借用来举例子。
如果要为连接服务器写 test harness,那么需要模拟客户(发起连接)、登录服务器(验证客户资料)、逻辑服务器(收发网游数据),有了这样的 test harness,可以方便地测试连接服务器的正确性,也可以方便地模拟其他各个服务器断开连接的情况,看看连接服务器是否应对自如。
同样的思路,可以为登录服务器写 test harness。(我估计不用为逻辑服务器再写了,因为肯定已经有自动测试了。)
3. 多 master 之间的二段提交
这是分布式容错的一个经典做法。用 test harness 能把 primary master 和 secondary masters 单独拎出来测试。在测试 primary master 的时候,test harness 扮演 name service 和 secondary masters。在测试 secondary master 的时候,test harness 扮演 name service、primary master、其他 secondary masters。可以比较容易地测试各种 failure 情况。如果不这么做,而直接部署多个 masters 来测试,恐怕很难做到自动化测试。
4. paxos 的实现
Paxos 协议的实现肯定离不了单元测试,因为涉及多个角色中比较复杂的状态变迁。同时,如果我要写 paxos 实现,那么 test harness 也是少不了的,它能自动测试 paxos 节点在真实网络环境下的表现,并且轻易模拟各种 failure 场景。
局限性
如果被测程序有 TCP 之外的 IO,或者其 TCP 协议不易模拟(比如通过 TCP 连接数据库),那么这种测试方案会受到干扰。
对于数据库,如果被测程序只是简单的从数据库 select 一些配置信息,那么或许可以在 test harness 里内嵌一个 in-memory H2 DB engine,然后让被测程序从这里读取数据。当然,前提是被测程序的 DB driver 能连上 H2 (或许不是大问题,H2 支持 JDBC 和 部分 ODBC)。如果被测程序有比较复杂的 SQL 代码,那么 H2 表现的行为不一定和生产环境的数据库一致,这时候恐怕还是要部署测试数据库(有可能为每个开发人员部署一个小的测试数据库,以免相互干扰)。
如果被测程序有其他 IO (写 log 不算),比如 DataNode 会访问文件系统,那么 test harness 没有能把 DataNode 完整地包裹起来,有些 failure cases 不是那么容易测试。这是或许可以把 DataNode 指向 tmpfs,这样能比较容易测试磁盘满的情况。当然,这样也有局限性,因为 tmpfs 没有真实磁盘那么大,也不能模拟磁盘读写错误。我不是分布式存储方面的专家,这些问题留给分布式文件系统的实现者去考虑吧。(测试 paxos 节点似乎也可以用 tmpfs 来模拟 persist storage,由 test case 填充所需的初始数据。)
其他用处
Test harness 除了实现 features 的回归测试,它还有别的用处。
*加速开发,提高生产力。
前面提到,如果有个新功能(增加一种新的 request type)需要改动两个程序,有可能造成相互等待:客户程序 A 说要先等服务程序 B 实现对应的功能响应,这样 A 才能发送新的请求,不然每次请求会被拒绝,无法测试;服务程序 B 说要先等 A 能够发送新的请求,这样自己才能开始编码与测试,不然都不知道请求长什么样子,也触发不了新写的代码。(当然,这是我虚构的例子。)
如果 A 和 B 都有各自的 test harness,事情好办了,双方大致商量一个协议格式,然后分头编码。程序 A 的作者在自己的 harness 里边添加一个 test case,模拟他认为 B 应有的响应,这个响应可以 hard code 某种常见的响应,不必真的实现所需的判断逻辑(毕竟这是程序 B 的作者该干的事情),然后程序 A 的作者可以编码并测试自己的程序了。同理,程序 B 的作者也不用等 A 拿出一个半成品来发送新请求,他往自己的 harness 添加一个 test case,模拟他认为 A 应该发送的请求,然后可以编码并测试自己的新功能。双方齐头并进,减少扯皮。等功能实现得差不多了,两个程序互相连一连,如果发现协议有不一致,检查一下 harness 中的新 test cases(这代表了 A/B 程序对对方的预期),看看那边改动比较方便,很快能解决问题。
*压力测试。
Test harness 稍作改进还可以变功能测试为压力测试,供程序员 profiling 用。比如反复不间断发送请求,向被测程序加压。不过,如果被测程序是 C++ 写的,而 test harness 是 Java 写的,有可能出现 test harness 占 CPU,而被测程序还跑得优哉游哉的情况。这时候可以单独用 C++ 写一个负载生成器。
小结
以单独的进程作为 test harness 对于开发分布式程序相当有帮助,它能达到单元测试的自动化程度和细致程度,又避免了单元测试对功能代码结构的侵入与依赖。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Solstice/archive/2011/04/25/6359748.aspx
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系SPASVO小编(021-61079698-8054),我们将立即处理,马上删除。

sales@spasvo.com