有webservice参与的系统的单元测试,最好使用mock object

手头上的一个项目,是以另外一个系统的webservice做为底层基础。

里面大约有50的操作,最终都要调用这些 web service。

大约有200个test case, 跑完一边居然要15~ 30分钟。因为调用一次WS,大约25秒左右。而且随着远程webservice 服务器的性能问题,这个时间还在增加。

程序员感觉很麻烦。因为调试的时候,如果远程 Webservice出了问题,本地也运行不了。

而且从过去的经验来看,这个webservice 不是很稳定。大约有3~ 5% 的出错率(网络原因造成)。很多时候需要跑很多次,才会明确结果。

所以这个项目越到后来,程序员就越不乐意运行单元测试, 草草的改了代码,最多运行一下对应的 model/controller spec, 然后提交。 最近一个月几乎没有跑过全部的webservice. 所以感觉非常麻烦。

另外一个原因, webserivce 的测试不好写。 不想test database那样可以轻易rollback, webservice 上的操作都需要手动的回复。某个 test case 提交一个POST增加测试数据,那么就必须在这个test case运行完之后删掉对应的数据。如果运行顺利还好说,不顺利的话,程序遇到了异常,直接跳过“删除对应数据”这一步,直接跳到下一个。

难于测试的程序就难于开发,所以项目 就越来越难不好做。

所以今天开始,决心引入mock object,最大限度的解决这个问题。

原则就是:

The mock object should also have exactly the same methods(public) that the original object has.

步骤: 视系统而定。有的系统是java,有的是rails, 这个系统是rails/rspec, 所以...

先找到一个最核心的resource(调用远程webservice的文件),然后mock it!~~~

1. 向 spec/support 中增加 一个mock object: mock_device_resource.rb ,(它所模拟的就是 device_resource.rb )

原则上它的接口跟 original object是一样的,不过由于我的程序有点儿复杂,所以我为它们增加了一个新参数: options. 例如:

原始的对象:

# original object
class DeviceResource < BaseResource
def initialize(cloudset)
end
def find(params)
...
end
end


模拟的对象:

# mock objects
# to inherite the Constants from ServerSettingResource
class MockServerSettingResource < ServerSettingResource
# do nothing, just compatible with the interface
def initialize(cloudset)
end

# +options+
# * :failed : if set to true, will return []. default is false
# * :blank_result : alias to : failed option
def find(params, options = {})
return [] if options[:failed] || options[:blank_result]
return [result_hash.merge(params)]
end
end


2. 建立这个mock object 的 rspec 文件, 把 原来的 device_resource_spec中的所有test case, 都COPY过去。

3. 运行测试,增加实现。。。直到 mock_device_resource_rspec.rb 的测试全部通过。

至此,mock_device_resource.rb 已经通过TDD的方式实现好了。在我这里看到的改善是(消耗的时间):

testcase1: 45 s =>0.x

testcase2: 15s => 0.x

testcase3: 51.4 => 0.x

testcase4: 25 =>0.0x

时间被大大缩短,原来需要 200秒的测试,现在 0.45s . 爽歪歪了。


4. 那么,那些调用真实存在的webservice 的test case,对象,改如何处理呢? 对于 original object, 进行一些预处理:加上一个调用时的参数,来跳过这个 spec 文件:

describe DeviceResource do
next if ENV['with_real_webservice'] != "true"
before do
#...
end
end

然后,调用的时候:
4.1 跳过该文件: $ bundle exec rspec spec
4.2 执行该文件: $ with_real_webservice=true bundle exec rspec spec

5. 事情还没完,接下来,我们要找出所有origin object被调用的地方,使用 mock object “尽量(因为还是有一些test case很难替换,或者这些test case测试的就是的 web service,所以,能替换多少就多少吧)”替换这些 original object.

6. 最后,修改spec_helper.rb,使用所有的 mock object屏蔽掉它们的original object.

# 因为 类名 实际上就是 Constants
if ENV['with_real_webservice'] != "true"
DeviceResource = MockDeviceResource
DifsSettingResource = MockDifsSettingResource
ServerSettingResource = MockServerSettingResource
end
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值