接口测试 Java、Python、Node.js 在接口测试过程中如何快速进行 diff 测试?

乾行 · 2018年08月07日 · 最后由 Cruise 回复于 2019年08月30日 · 3426 次阅读

背景

在接口测试过程中,当所测接口存在多个版本时,经常遇到需要对新旧接口返回值进行对比测试(diff 测试)。

方法

方法 1. 将新旧接口返回值以文件存储,然后利用文件对比、json 对比的工具逐一比较进行测试。
方法 2. 自动化测试,使用 python、javascript、java 中断言进行对比。

java:junit+fastjson,使用 Assert.assertEquals 进行对比测试

  • 1. 示例代码
package com.test.apitest;

import com.alibaba.fastjson.JSON;
import  org.junit.*;
import java.util.Map;

public  class JunitJsonDiffTest {

    @Test
    public  void  test_json_diff_sub_nodes(){
        String oldJson = "{\"user\":{\"name\":\"test\",\"age\":10},\"hello\":\"world\",\"success\":true}";
        String newJson = "{\"success\":true,\"hello\":\"world\",\"user\":{\"name\":\"test\",\"age\":10}}";

        Object oldJsonVo = JSON.parseObject(oldJson);
        Object newJsonVo = JSON.parseObject(newJson);
        // json字段顺序虽然不一样,但是对比结果相同
        Assert.assertEquals(oldJsonVo,newJsonVo);
    }

    @Test
    public  void  test_json_with_date(){
        String oldJson = "{\"hello\":\"你好\",\"success\":true,\"birthDay\":\"1995-06-15 10:05:00\"}";
        String newJson = "{\"hello\":\"你好\",\"success\":true,\"birthDay\":\"1995-06-15 20:05:00\"}";

        Object oldJsonVo = JSON.parseObject(oldJson);
        Object newJsonVo = JSON.parseObject(newJson);
        // 对比过程中需要考虑是否支持中文
        Assert.assertEquals(oldJsonVo,newJsonVo);
    }

}
  • 2. 对比效果

  • 3. 使用 mvn 命令生成 html 报告
    pom.xml 文件中增加 maven-surefire-report 依赖项:

<dependency>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-report-plugin</artifactId>
  <version>2.22.0</version>
</dependency>

生成 html 报告命令:

mvn clean  surefire-report:report
  • 4. html 报告

python:unitest+json,使用 assertDictEqual 进行对比测试

  • 1. 示例代码
import json

class TestDictCompare(unittest.TestCase):
    def test_jsondiff_same(self):
      self.maxDiff=None
      oldJson = json.loads('{"title":"com.tencent.news","fullTitle":"Androbugs report com.tencent.news","timedOut":false,"duration":0,"state":"passed","speed":"fast","pass":true,"fail":false,"pending":false}')
      newJson = json.loads('{"title":"com.tencent.news","fullTitle":"Androbugs report com.tencent.news","timedOut":false,"duration":0,"state":"passed","speed":"fast","pass":true,"fail":false,"pending":false}')
      self.assertDictEqual(oldJson,newJson)

    def test_jsondiff_different(self):
      self.maxDiff=None
      oldJson = json.loads('{"title":"com.tencent.news","fullTitle":"Androbugs report com.tencent.news","timedOut":false,"duration":0,"state":"passed","speed":"fast","pass":true,"fail":false,"pending":false}')
      newJson = json.loads('{"title":"com.tencent.news2","fullTitle":"Androbugs report com.tencent.news","timedOut":false,"duration":0,"state":"passed","speed":"fast","pass":true,"fail":false,"pending":false}')
      self.assertDictEqual(oldJson,newJson)


if __name__ == '__main__':
  unittest.main()
  • 2. 运行结果

  • 3. 使用 pip 安装 pytest-html 包生成 html 报告

 pip install pytest-html
pytest --html=sondiff.html test\test_jsondiff.py
  • 4. 生成的 html 报告

javascript:mocha+chai,使用 deepEqual 进行对比测试

  • 1. 示例代码
var assert = require("chai").assert;

describe("json diff", function () {
  it("json相同", function () {
    var oldJson = JSON.parse('{"title":"com.tencent.news","fullTitle":"Androbugs report com.tencent.news","timedOut":false,"duration":0,"state":"passed","speed":"fast","pass":true,"fail":false,"pending":false}')
    var newJson = JSON.parse('{"title":"com.tencent.news","fullTitle":"Androbugs report com.tencent.news","timedOut":false,"duration":0,"state":"passed","speed":"fast","pass":true,"fail":false,"pending":false}')
    assert.deepEqual(oldJson, newJson)
  })

  it("json不同", function () {
    var oldJson = JSON.parse('{"title":"com.tencent.news","fullTitle":"Androbugs report com.tencent.news","timedOut":false,"duration":0,"state":"passed","speed":"fast","pass":true,"fail":false,"pending":false}')
    var newJson = JSON.parse('{"title":"com.tencent.news2","fullTitle":"Androbugs report com.tencent.news","timedOut":false,"duration":0,"state":"passed","speed":"fast","pass":true,"fail":false,"pending":false}')
    assert.deepEqual(oldJson, newJson)
  })
})
  • 2. 运行结果

  • 3. 使用 mochawesome 生成 html 报告

npm install mochawesome --save-dev
mocha test\jsondiff.test.js --reporter mochawesome
  • 4. 生成的 html 报告

如何快速进行 diff 测试?

原理:在 JAVA、python 中将需要对比的接口 URL 进行参数化,使用一个用例来进行 diff 测试。在 JavaScript 中将接口 url 及其参数放到一个参数数组中,使用数组 forEach 方法来进行对比测试。

JAVA 代码

package com.test.apitest;

import com.alibaba.fastjson.JSON;
import org.junit.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.util.Map;

import com.github.kevinsawicki.http.HttpRequest;

public class BatchApiTest {
    @DataProvider
    public Object[][] data() {
        Object[][] diffApis = new Object[][]{
                {
                        "https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v1/demo",
                        "https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v2/demo"
                },
                {
                        "https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v1/hello",
                        "https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v2/hello"
                },
                {
                        "https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v1/user",
                        "https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v2/user"
                },
                {
                        "https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v1/1/list",
                        "https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v2/1/list"
                },
                {
                        "https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v1/2/list",
                        "https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v2/2/list"
                },
                {
                        "https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v1/3/list",
                        "https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v2/3/list"
                },
        };
        return diffApis;
    }

    @Test(dataProvider = "data")
    public void test_apiDiff(String oldApiUrl, String newApiUrl) {
        String oldJsonString = HttpRequest.get(oldApiUrl)
                .accept("application/json")
                .body();
        String newJsonString = HttpRequest.get(newApiUrl)
                .accept("application/json")
                .body();
        Map<String,Object> oldJsonVo = JSON.parseObject(oldJsonString);
        Map<String,Object> newJsonVo = JSON.parseObject(newJsonString);
        Assert.assertEquals(oldJsonVo,newJsonVo);
    }
}

Python 示例

# -*- coding:utf-8 -*-
import unittest
import requests
from parameterized import parameterized, param
diffApis = [
    # diff apis
    {
        "oldapi":"https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v1/demo",
        "newapi":"https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v2/demo"
    },
    {
        "oldapi":"https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v1/hello",
        "newapi":"https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v2/hello"
    },
    {
        "oldapi":"https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v1/user",
        "newapi":"https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v2/user"
    },
    {
        "oldapi":"https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v1/1/list",
        "newapi":"https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v2/1/list"
    },
    {
        "oldapi":"https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v1/2/list",
        "newapi":"https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v2/2/list"
    },
    {
        "oldapi":"https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v1/3/list",
        "newapi":"https://easy-mock.com/mock/5b6b06f9a40bfb27425bbb6a/jsondiff/v2/3/list"
    },
]


class TestParameterized(unittest.TestCase):
    @parameterized.expand(
        [param("diff test",kv) for kv in diffApis])
    def test_batchdiff(self, _, kv):
        self.maxDiff = None
        oldRes = requests.get(kv["oldapi"])
        newRes = requests.get(kv["newapi"])
        self.assertDictEqual(oldRes.json(), newRes.json())


if __name__ == '__main__':
    unittest.main(verbosity=2)

Node.js 示例

var assert = require('chai').assert;
var request = require('request');

describe("Node.js版本批量接口diff", function () {
  this.timeout(0);
  [
    // diff apis
    {
      oldapi: "https://mtj.baidu.com/data/mobile/trend?dimension=brand&platformId=0&rankId=16&stattime=1527782400",
      newapi: "https://mtj.baidu.com/data/mobile/trend?dimension=brand&platformId=0&rankId=16&stattime=1527782400"
    },
    {
      oldapi: "https://mtj.baidu.com/data/mobile/trend?dimension=brand&platformId=0&rankId=16&stattime=1527782400",
      newapi: "https://mtj.baidu.com/data/mobile/trend?dimension=brand&platformId=0&rankId=4&stattime=1527782400"
    },
    {
      oldapi: "https://mtj.baidu.com/data/mobile/trend?dimension=brand&platformId=0&rankId=16&stattime=1527782400",
      newapi: "https://mtj.baidu.com/data/mobile/trend?dimension=brand&platformId=0&rankId=23&stattime=1527782400"
    },
    {
      oldapi: "https://mtj.baidu.com/data/mobile/trend?dimension=brand&platformId=0&rankId=16&stattime=1527782400",
      newapi: "https://mtj.baidu.com/data/mobile/trend?dimension=brand&platformId=0&rankId=12&stattime=1527782400"
    },
    {
      oldapi: "https://mtj.baidu.com/data/mobile/trend?dimension=brand&platformId=0&rankId=16&stattime=1527782400",
      newapi: "https://mtj.baidu.com/data/mobile/trend?dimension=brand&platformId=0&rankId=2&stattime=1527782400"
    },
    {
      oldapi: "https://mtj.baidu.com/data/mobile/trend?dimension=brand&platformId=0&rankId=16&stattime=1527782400",
      newapi: "https://mtj.baidu.com/data/mobile/trend?dimension=brand&platformId=0&rankId=15&stattime=1527782400"
    },
    {
      oldapi: "http://wis.qq.com/weather/common?callback=jQuery111105922904533110209_1533657946519&weather_type=observe%7Cforecast_24h%7Cair&source=pc&province=%E5%8C%97%E4%BA%AC%E5%B8%82&city=%E5%8C%97%E4%BA%AC%E5%B8%82&county=&_=1533657946520",
      newapi: "http://wis.qq.com/weather/common?callback=jQuery111105922904533110209_1533657946519&weather_type=observe%7Cforecast_24h%7Cair&source=pc&province=%E5%8C%97%E4%BA%AC%E5%B8%82&city=%E5%8C%97%E4%BA%AC%E5%B8%82&county=&_=1533657946520"
    },
    {
      oldapi: "http://wis.qq.com/weather/common?source=pc&weather_type=observe|alarm&province=%E5%8C%97%E4%BA%AC%E5%B8%82&city=%E5%8C%97%E4%BA%AC%E5%B8%82&county=&callback=jQuery112008061541882788377_1533657948645&_=1533657948647",
      newapi: "http://wis.qq.com/weather/common?source=pc&weather_type=observe|alarm&province=%E5%8C%97%E4%BA%AC%E5%B8%82&city=%E5%8C%97%E4%BA%AC%E5%B8%82&county=&callback=jQuery112008061541882788377_1533657948645&_=1533657948647"
    }
  ].forEach(function (kv) {
    it(kv.newapi + " vs " + kv.oldapi, function (done) {
      request.get(kv.newapi, {
          json: true
        },
        function (err, res, body) {
          request.get(kv.oldapi, {
              json: true
            },
            function (oldapiError, oldapiRes, oldapiBody) {
              assert.deepEqual(body, oldapiBody);
              done();
            }
          );
        }
      );
    });
  });
});

备注

  1. pytest、pip 如何使用请自行学习,本文不做介绍;
  2. mocha、npm 如何使用请自行学习,本文不做介绍;
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 11 条回复 时间 点赞
乾行 #14 · 2018年08月07日 Author

java 的版本没有去实现,感兴趣的小伙伴自行 github 搜索 json 对比的开源项目

12楼 已删除

不错,楼主应该说的是主要的对比方法,但具体的执行方法有机会可以在补充一下哈
2 点
1、不同版本的接口做 diff 测试,基本上至少要 2 套或以上测试环境,设 1 套为旧接口生产环境 1 套新接口 diff 环境,你这里是什么方式将流量从旧接口服务器复制到新接口
2、新接口环境的测试数据如何管理,还是说在 diff 环境做数据挡板,还有怎么保障流量到新接口走过的子调用和旧接口子调用的差异,数据比对相同不一定就是 diff 的真正结果

乾行 #10 · 2018年08月07日 Author
terrychow 回复

不同版本测试接口 diff 可以尝试下述思路:
思路 1、部署两套测试环境,可以让 oldJson 等于旧版本接口返回值,newJson 等于新接口返回值,然后进行对比;
思路 2、如果只有一套测试环境,可以先使用 CI 工具部署旧版本源代码构建的应用,遍历需要 diff 的接口,存储各个接口返回值为 json(存库也可)。接下来部署新版本源代码构建的应用,遍历所有接口,对每一个接口返回值进行对比。
思路 3、如果允许使用线上发布的版本接口返回值(通常情况下面向公众的 App、web 应用均可使用线上版本)进行对比,思路与思路 1 相似。

terrychow 回复

第二点问题回复如下,仅供参考。
1、测试数据如何管理?
这个可以使用 json 文件、Excel、csv、数据库均可,看个人喜好,怎么快就怎么用。
2、如何保证新接口子调用是否一致?
方法 1、让开发在调用过程中打印日志,通过检查日志来确认调用链路是否一致。
方法 2、工作过程中如果有代码查看权限,直接确认代码变动可能更快;
欢迎其他小伙伴补充

推荐大家使用 mocha 搞定 diff 测试
1、高效,例如 v1、v2 两套接口,url 中除了版本号不一样外,其他参数相同,稍微组织一下代码,一个 forEach 便可搞定成百上千的接口 diff 测试。

2、测试报告漂亮

3、Google、Microsoft、Facebook 都在用,思考一下为什么世界顶级大公司都在用 mocha?
问题抛给各位了

此外,文中 mocha 进行 diff 测试过程中参数化使用的是数组,当然可以选择 json 文件、csv 文件、Excel 文件、数据库数据进行参数。

提几个建议吧,你的代码实现:

  1. 写好注释,让代码可读性更强。
  2. 代码良好的设计,比如要穿插 Post 等,Http 请求头不同,Https 协议等等,代码如何更优雅。
  3. 接口返回值比对是在接口成功返回之后做的事,而接口的成功返回需要各种各样的断言,要加上。
zyanycall 回复

收到你的建议,周末有时间再去改了

仅楼主可见

我想问几个问题:python 版本
1、如果每个接口传入的参数不同怎么处理,每个接口写一个 test_diff_** 接口的方法
2、如果是在一个方法里循环调用接口地址进行请求的话,测试报告中如何能体现多个接口的用例记录,而且一旦 不一致,系统就会直接抛出异常,标识该用例是失败的,如何能快速把多个接口进行数据比对

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