背景

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

方法

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

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

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);
    }

}
<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

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

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()
 pip install pytest-html
pytest --html=sondiff.html test\test_jsondiff.py

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

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)
  })
})
npm install mochawesome --save-dev
mocha test\jsondiff.test.js --reporter mochawesome

如何快速进行 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 如何使用请自行学习,本文不做介绍;


↙↙↙阅读原文可查看相关链接,并与作者交流