通用技术 接口测试中数值 diff 和结构 diff 方法

思寒_seveniruby · 2015年04月29日 · 最后由 森森带你飞 回复于 2020年02月21日 · 3343 次阅读
本帖已被设为精华帖!

今年的 QCon 上, 七牛的人介绍了下他们的接口测试体系. 我发现碰巧和我们公司做的一样.
他说他的测试体系是用 go 实现的, 并且说思想是来自于...

不过类似的测试框架 ruby 早就有了. 今天就给大家分享下.
开源项目地址 https://github.com/okitan/capybara-json
样例代码如下

require 'capybara/json'
include Capybara::Json

Capybara.current_driver = :httpclient_json
Capybara.app_host = 'http://example.com'
post '/', { "this is" => "json" } # POST 'http://example.com/'
json     #=> parsed json response
raw_json #=> raw response body

get  '/errors/400'
status_code #=> 400
get! '/errors' #=> raise Capybara::Json::Error

get  '/errors', {}, { 'header' => '' } # set request headers
response_headers #=> get response headers

是不是非常的简单. 我们公司的接口测试就是基于这个小框架做的.
capybara 是什么那. 是一个新的测试框架.他也是 Rails 的作者极力推崇的一个框架. 他的功能太强大了, 以至于我不能在此介绍它.
大家可以自己去 github 上看看.

我在公司开发了一个接口测试工具. 这个工具的实现思路如下.
利用 fiddler 或者其他的反向代理工具, 截获各个模块之间的请求. 然后保存下来.比如 fiddler 就有个导出为 HAR 格式的功能.
然后解析这个 HAR, 发送之前录制好的请求, 然后拿系统实际返回的结果与之前 HAR 中保存的结果进行比对.
为了统计结果, 所以使用了 xunit 框架. 因为这是数据驱动的方式, 不是硬编码. 所以需要用到一个单测的 hack 技巧. 动态生成测试用例.

#coding: utf-8
require 'rubygems'
require 'json'
require 'pp'
require 'minitest'
require 'minitest/autorun'
require 'capybara/json'
include Capybara::Json

#把返回的json解析为结构, 去掉具体的数字. 比如"xxx"会被解析为"String"
def to_struct(h)
    if h.class==Hash
        new=h.clone
        h.map do |k,v|
            new[k]=to_struct(v)
        end
    end
    if h.class==Array
        new=[]
        h.each do |x|
            new << to_struct(x)
        end
    end
    if h.class!=Array && h.class!=Hash
        new=h.class.to_s
    end
    new 
end

DynamicTest = Struct.new :name, :expected, :actual
DYNAMIC_TESTS = []

Capybara.current_driver = :httpclient_json
#Capybara.app_host = 'http://10.128.6.44:18080'

host="oneapm.com"
index=0
Dir["data/*.har"].each do |data_file|  
    content=IO.read(data_file, :encoding=>"UTF-8")
    obj = JSON.parse(content[1..-1])
    1.times do |i|
        now=Time.now.strftime("%s").to_i
        obj['log']['entries'].each do |entry|
            index+=1
            method=""
            entry['request']['url'].sub!("10.128.6.44:18080", host)

            begin
                now=Time.now.strftime("%s").to_i-60
                if method=='metric_data' 
                    data[1]=(now-60)
                    data[2]=now
                end
                post  entry['request']['url'], data.to_json
                testcase_name="test_#{data_file.gsub('/', '_')}_#{method}_#{index.to_s}"
                p "create testcase #{testcase_name}"

#动态生成值对比的单测用例
                DYNAMIC_TESTS << DynamicTest.new(testcase_name+"_diff_value", raw_json, entry['response']['content']['text'])
                begin
                    expect_s=to_struct(JSON.parse(entry['response']['content']['text']))
                rescue
                    expect_s="json parse error"
                end
                begin
                    actual_s=to_struct(JSON.parse(raw_json))
                rescue
                    actual_s="json parse error"
                end
#动态生成结构对比的单测用例
                DYNAMIC_TESTS << DynamicTest.new(testcase_name+"_diff_struct", expect_s, actual_s)
            rescue
                p "exception"
                p entry['request']['url']
            end

        end
        sleep 1
    end
end

#动态生成单测方法
class HARDiff <  MiniTest::Test
    class << self
        def create_method(t)
            define_method(t.name) do
                assert_equal t.expected, t.actual
            end
        end
    end
end
DYNAMIC_TESTS.each do |t|
    HARDiff.create_method t
end

代码经过裁剪, 仅供参考

代码非常的少, 主要是特点如下

  1. 可以利用 fiddler 或者 chrome 导出某个系统的接口请求列表. 导出为 HAR 格式
  2. 读取 HAR 格式, 进行接口 diff 测试, 这种测试属于专项测试. 他只是为了保证新老系统的接口一致.
  3. 接口 DIff 方法分两个模式.一个是相同的请求返回的内容完全相同. 一个是返回的内容格式相同即可. 有些值可以不同. 我把返回的 json 结果进行了变幻.支持结构对比.
  4. 重新发送请求的时候,有些字段如果需要修改比如时间戳等, 需要自己自定义.
  5. 利用 ci-reporter 插件把单测的结果导出为 xml 并放到 jenkins 上解析.

使用场景是
提前录制好系统的交互. 然后新版本环境上线. 使用录制好的数据回放请求并验证接口的变更情况. 这种方式简单粗暴.但是可以保证接口的稳定.

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 15 条回复 时间 点赞

牛 X,我一直以为接口测试都是自己写代码一个一个接口过,土鳖了。。。

#1 楼 @yangchengtest 这个是做 DIFF 的~

#2 楼 @anikikun 帅哥 show 下你的 py 版~

这么说啊,主要是觉得思寒从报文中提取测试源数据这个很牛 X。
感觉我们写 API,都是自己写 API 接口,自己设计入参,判断返回。没有想过中间去抓取数据,再作为源数据传入。
我个人有个疑问啊,这样的话,如果目标 IP 变化,或者 HTTP 的 SESSIONID 变化,这样不就歇菜了,源报文也要变化啊。
这个我暂时理解不了,求解答~~~
是不是我理解的有问题啊?这个主要是测试 DIFF 的?具体数据怎么驱动还是要代码支持?

#4 楼 @yangchengtest 接口测试中肯定有时间戳或者 session 相关的东西 需要加一些自定义的逻辑控制 因为规则每家公司都不同 所以需要自定义 我在文中第四点提到了

#4 楼 @yangchengtest 从报文中提取数据 这个是最差的办法 但是适合基于文本的协议 更复杂的比如 jms socket mysql 这种协议有更好的工具 需要用到插桩技术 从 api 拦截并重新构造

@seveniruby 基本上是明白了,谢谢思寒的解答。~

看不懂,有 python 就好了,先收藏

@284772894 你的基于 Python 的接口测试框架具体是啥,开源吗?是不是 Python+Nosetests?我最近用的也是用 Python 的接口测试框架

看不懂。。。好复杂。

jmeter 更好用

这种新老接口的对比测试,当时是用 RF+Requests 做的,自己写了一个 Json 对比的关键字。对两个接口返回的 Json 格式数据进行遍历对比。对比数据类型会包含,字典,列表,字符串,整形,浮点类型。可以实现全量数据的对比,但重购三次这个关键字代码,来减少代码量。

可能我们的 http 接口没有那么多,我们的回归设计思想:数据源相同,结果的 string 对比 相同即为 pass 不同即为 fail 不知道这个方法是不是可以

比 diffy,有啥优点?

想问下接口之间依赖的的参数化是如何实现的?比较的都是幂等接口吗?

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册