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

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

背景

在接口测试过程中,当所测接口存在多个版本时,经常遇到需要对新旧接口返回值进行对比测试(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 条回复 时间 点赞

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

仅楼主可见
乾行 #12 · 2018年08月13日 Author
zyanycall 回复

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

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

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

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

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

2、测试报告漂亮

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

terrychow 回复

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

terrychow 回复

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

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

3楼 已删除

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

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