在接口自动化测试中,如何利用Pytest + JSON Schema 进行接口响应断言

蔚来汽车数字座舱,通过服务端TSP系统集成了很多供应商的系统,比如天气、音乐、视频等娱乐系统。数字座舱调用TSP封装的供应商接口实现对天气的查询,音乐和视频的播放。TSP与供应商之间要共同遵守一个契约,保证提供给数字座舱的数据结构的稳定。

TSP对供应商系统的Response透传给数字座舱,因此在进行TSP系统接口测试的过程中,一个重要的检查点,就是保证提供给数字座舱的数据结构的稳定性,例如返回字段要完整、字段数据类型要符合约定,避免因供应商的改动,返回不符合约定的Response给数字座舱导致数字座舱异常。从而保证当供应商接口的响应发生了变更,通过接口测试能够第一时间发现,哪怕供应商没有及时通知到TSP。

因为数字座舱、TSP以及供应商之间,都是采取JSON作为数据交换的格式,因此,TSP接口测试中,保证Response字段的完整性、数据类型的合法性,就是检查JSON格式的完整性和合法性。

JSON Schema 是描述JSON数据结构的一种格式,通过JSON Schema可以描述JSON的字段以及字段数据类型等信息。因此,校验JSON Schema就可以校验Response是否符合约定。这种测试也常被叫做契约测试。

本文将介绍,如何通过JSON Schema对TSP接口的Response进行契约测试。

01 — 什么是JSON Schema

JSON Schema是一种用来描述JSON数据的一种JSON数据结构,它本身也是一个JSON格式。JSON Schema也有版本,最新版的版本是 Draft 7,此前还有Draft 6,Draft 4 和Draft 3。

一个JSON格式的数据,通常是由以下一种或多种数据类型组成的:

  1. string
  2. Numeric(integer、number)
  3. object
  4. array
  5. boolean
  6. null

JSON Schema中对上面的6种数据类型,都有相应的属性对其进行描述。下面是一个JSON数据,它是由object类型的data,Numeric类型的cur_num,string类型的keyword,boolean类型的vip,array类型的list组成,list中的数据是object类型:

{
    "data": {
        "cur_num": 10,
        "keyword": "朴树",
        "vip": true,
        "list": [
            {
                "album_id": 2032482,
                "publish": "2018-11-13T20:20:39+00:00"
            },
            {
                "album_id": 7986,
                "publish": "2018-11-13T20:20:39+00:00"
            }
        ]
    }
}

下面的JSON Schema可以描述上面JSON格式数据:

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties": {
    "data": {
      "type": "object",
      "properties": {
        "cur_num": {
          "type": "integer",
          "minimum": 1,
          "maximum": 10,
          "exclusiveMaximum": false
        },
        "keyword": {
          "type": "string",
          "minLength": 1,
          "maxLength": 10
        },
        "vip": {
          "type": "boolean"
        },
        "list": {
          "type": "array",
          "minItems": 1,
          "maxItems": 10,
          "items": {
            "type": "object",
            "properties": {
              "album_id": {
                "type": "integer"
              },
              "publish": {
                "type": "string",
                "format":"date-time"
              }
            },
            "required": [
              "album_id",
              "publish"
            ]
          }
        }
      },
      "required": [
        "cur_num",
        "keyword",
        "vip",
        "list"
      ]
    }
  },
  "required": [
    "data"
  ]
}

JSON Schema还是很容易看懂的,现在我们简单来介绍一下:

  1. "$schema"表示这个JSON Schema是使用的draft-04版本的规范;
  2. data的type是object类型;
  3. data包含的properties有cur_num、keyword、vip和list,这四个属性都是required,也就是必须包含的;
  4. cur_num的type是个integer(整型),它的取值范围最小值是1(“minimum”: 1,),最大值是10(“maximum”: 10,),包含10(“exclusiveMaximum”: false);
  5. keyword的类型是string(字符串),最小长度是1(“minLength”: 1),最大长度是10(“maxLength”: 10);
  6. vip的类型是布尔型;
  7. list的类型是数组array,至少包含1个元素(“minItems”: 1),最多包含10个元素(“maxItems”: 10)每一个元素都是object类型,包含album_id和publish两个必须属性
  8. publish是个string类型,格式是个日期时间格式(“format”:“date-time”)。

比如对于string类型的数据,JSON Schema可以通过format对常见的字符串进行描述,比如描述时间和日期的"date-time",描述电子邮件的"email",描述主机的"hostname",描述IP地址的"ipv4"和"ipv6",描述URI的"uri"。如果不属于常见的字符串格式,还可以通过pattern正则表达式来描述,比如"pattern": "^(\([0-9]{3}\))?[0-9]{3}-[0-9]{4}$“可以用来描述”(888)555-1212"这样的字符串。

通过上面的讲解,我想大家应该可以用JSON Schema来描述一个JSON字符串了吧。更全面的JSON Schema的语法可以参考http://json-schema.org/understanding-json-schema/。

上面的JSON Schema能否描述前面的JSON字符串呢?可以通过在线的一个Validator进行验证一下,我们将上面的JSON Schema和JSON复制粘贴到这个网站https://www.jsonschemavalidator.net/上,如果网页下方出现No errors found. JSON validates against the schema,则表示JSON字符串符合JSON Schema的描述。

02 — 自动化测试中如何断言

在自动化测试中,如何通过代码方式对JSON格式的Response进行JSON Schema断言呢?因为我平时都是用Python进行自动化测试的,今天就来介绍一下python语言中JSON Schema断言的方式。

需要安装一个jsonschema软件包:

pip install jsonschema

假设respose_data是通过某一个接口的response,我们对其进行JSON Schema校验,使用的JSON Schema是schema,自动化测试代码可以写成如下所示:

# content of test_json_schema.py
from jsonschema import validators
​
​
def test_json_schema_validator():
    schema = {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "type": "object",
        "properties": {
            "data": {
                "type": "object",
                "properties": {
                    "cur_num": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 10,
                        "exclusiveMaximum": False
                    },
                    "keyword": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 1
                    },
                    "vip": {
                        "type": "boolean"
                    },
                    "list": {
                        "type": "array",
                        "minItems": 1,
                        "maxItems": 10,
                        "items": {
                            "type": "object",
                            "properties": {
                                "album_id": {
                                    "type": "integer"
                                },
                                "publish": {
                                    "type": "string",
                                    "format": "date-time"
                                }
                            },
                            "required": [
                                "album_id",
                                "publish"
                            ]
                        }
                    }
                },
                "required": [
                    "cur_num",
                    "keyword",
                    "vip",
                    "list"
                ]
            }
        },
        "required": [
            "data"
        ]
    }
    respose_data = {
        "data": {
            "cur_num": 10,
            "keyword": "朴树",
            "vip": True,
            "list": [
                {
                    "album_id": 2032482,
                    "publish": "2018-11-13T20:20:39+00:00"
                },
                {
                    "album_id": 7986,
                    "publish": "2018-11-13T20:20:39+00:00"
                }
            ]
        }
    }
    va = validators.Draft4Validator(schema)
    va.validate(respose_data)

注意,在Python语言中boolen值是False和True,首字母大写。

执行上面的代码将看到如下输出:

tests/test_json_schema.py::test_json_schema_validator PASSED             [100%]

如果我们将respose_data修改一下,来模拟接口的response发生了变化,比如"cur_num": 10,改成"cur_num": 20,再次执行上面的代码,则会看到测试不通过,并输出如下信息:

tests/test_json_schema.py:78: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
​
self = .validators.create..Validator object at 0x106307710>
args = ({'data': {'cur_num': 20, 'keyword': '朴树', 'list': [{'album_id': 2032482, 'publish': '2018-11-13T20:20:39+00:00'}, {'album_id': 7986, 'publish': '2018-11-13T20:20:39+00:00'}], 'vip': True}},)
kwargs = {}, error = '20 is greater than the maximum of 10'>
​
    def validate(self, *args, **kwargs):
        for error in self.iter_errors(*args, **kwargs):
>           raise error
E           jsonschema.exceptions.ValidationError: 20 is greater than the maximum of 10
E           
E           Failed validating 'maximum' in schema['properties']['data']['properties']['cur_num']:
E               {'exclusiveMaximum': False,
E                'maximum': 10,
E                'minimum': 1,
E                'type': 'integer'}
E           
E           On instance['data']['cur_num']:
E               20

从输出的错误信息可以看出,instance[‘data’][‘cur_num’]的数值是20,不满JSON Schema中对其描述的最大值不能超过10的规范。因此断言失败。

03 — 总结

文本先介绍了JSON Schema如何描述JSON格式的数据,接着对Python语言中,如何使用jsonschema对接口的Response进行JSON Schema断言。在日常测试工作中,如果被测服务是对上游或者第三方供应商的Response进行透传,那么在测试这个被测服务时,一般就是对Response进行JSON Schema断言,来保证Response的数据结构稳定和合乎契约。

参考资料

https://juejin.im/entry/5c66975de51d4574b5550f1f
https://www.jianshu.com/p/1711f2f24dcf
https://www.jianshu.com/p/8d4ae03d92fb
https://json-schema.org/understanding-json-schema/index.html#
https://www.liquid-technologies.com/online-json-to-schema-converter
https://www.jsonschemavalidator.net/

你可能感兴趣的:(pytest单元测试框架,pytest,jsonschema,接口断言)