契约测试笔记

契约测试笔记

字面上意思是签订一份双方甚至多方的的一份约定/合同,最早可以出现在买卖,抵押等活动,如果一方违背了这份契约,那就得付出相应的处罚(锅你来背)

在软件开发中,测试被测系统崩溃,产品需求变更,项目进度加快等我们不可抗的因素导致我们测试被拖慢,开发压榨测试时间,测试时间不够(这也是时常发生的问题),后来有一种新的思想--测试替身,不再调用还未开发的api接口,使用测试替身模拟接口的实现(mock就是一种测试替身的手段),但是mock完全不能代替开发写的api,如开发变更了接口,那我们的mock就白写了,而且我们还不知道开发改了,只有调用报错了才知道,那么这样想只要真实api和mock返回数据是一样的就可以解决这个问题吗?这其实是无法保证的

由此诞生了消费者驱动的契约测试(Consumer-Driven Contracts,简称CDC)

一种是根据消费者驱动契约,还有一种是生产者驱动契约,我们主要使用的方式是消费者驱动契约的方式,原因是能够验证服务是否满足我们消费方的期待,本质是从利益者和使用者的角度,体验,目标作为出发点,满足我们一些业务的价值实现.当然生产者驱动契约模式是有的,但我更喜欢两者来维护这份契约,而不是一方提出

为什么要做契约测试?

可能说不上为什么要去做,可能是如今微服务架构复杂,我们平时看百度一下的页面,不单单是测前端哪个按钮能点不能点,背后大量的api调用,大量的微服务嵌套关系,这是如今各种高内聚低耦合的服务架构发展,而且所有开发人员来自不同城市,不同地区,不得不指定契约来做这些事情,如不同区域调用同一份契约,一个接口从1.0变更为2.0,我们需要约定俗成,2.0必须要覆盖1.0的测试需求,而且保证所有的返回数据都能测试到,那样我们说契约测试才会有效果

所以说白了来说就是我们消费者提供一个json/yml格式的文件,再由生产者来实现契约,这样之后就能够实现我们之间说的两者之间的同步问题,当版本迭代或者是接口更新时,新版本中的契约同步是通过旧版接口测试新版的接口,当测试通过后再更新新版本的契约,上线时切换到新接口中

 

契约测试的不适合场景

  公共api服务

  性能测试

  消费者和生产者没有沟通渠道

 

契约测试工具

Pact

  基于json文件格式

Swagger

  基于yml文件格式

这里我来着重学swagger,首先使用方便,对python-django,flask都有扩展,其次它可以根据注释来生成文档,比如github上一些api接口文档开发人员不会开发完再回头重新写一遍文档,这种都是用工具自动就可以生成的,当然也可以用yml文档生成,安装生成也很简单,只要node.js和相关扩展就行了,当然如果不急着做契约测试,做出一份漂亮的接口文档给开发看,心里也是乐滋滋的

# pip install flasgger

# 简易示例
import random
from flask import Flask, jsonify, request
from flasgger import Swagger, swag_from
import json

app = Flask(__name__)
Swagger(app)

#swagger为本地文档yml文件的情况
@app.route('/aaa/<string:language>/', methods=['GET'])
@swag_from("api_get.yml")
def aaa(language):
    language = language.lower().strip()
    features = [
        "awesome", "great", "dynamic",
        "simple", "powerful", "amazing",
        "perfect", "beauty", "lovely"
    ]
    size = int(request.args.get('size', 1))
    if language in ['php', 'vb', 'visualbasic', 'actionscript']:
        return "An error occurred, invalid language for awesomeness", 500
    return jsonify(
        language=language,
        features=random.sample(features, size)
    )


#多methods的情况
@app.route('/test/', methods=['POST', 'GET', ])
def test():
    '''
    这里写接口简单描述信息
    这里写详细的接口信息xxxxxxxxxxxxxxxxxx
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    ---
    tags:
      - Awesomeness Language API
    parameters:
      - name: username
        in: path
        type: string
        required: true
        description: 这里填入参描述  姓名
      - name: password
        in: query
        type: int
        description: 这里填入参描述  密码
    responses:
      500:
        description: 这里填描述 500了
      200:
        description: A ok了
    '''
    if request.method.lower() == 'get':
        res = {'status': '这是get请求'}
    elif request.method.lower() == 'post':
        res = {'status': '这是post请求'}
    else:
        res = {'status': '不支持该方法'}
    return json.dumps(res,ensure_ascii=False,indent=4)


#带参数的get请求
@app.route('/api/<string:language>/', methods=['GET'])
def index(language):
    """
    This is the language awesomeness API
    Call this api passing a language name and get back its features
    ---
    tags:
      - Awesomeness Language API
    parameters:
      - name: language
        in: path
        type: string
        required: true
        description: The language name
      - name: size
        in: query
        type: integer
        description: size of awesomeness
    responses:
      500:
        description: Error The language is not awesome!
      200:
        description: A language with its awesomeness
        schema:
          id: awesome
          properties:
            language:
              type: string
              description: The language name
              default: Lua
            features:
              type: array
              description: The awesomeness list
              items:
                type: string
              default: ["perfect", "simple", "lovely"]

    """
    language = language.lower().strip()
    features = [
        "awesome", "great", "dynamic",
        "simple", "powerful", "amazing",
        "perfect", "beauty", "lovely"
    ]
    size = int(request.args.get('size', 1))
    if language in ['php', 'vb', 'visualbasic', 'actionscript']:
        return "An error occurred, invalid language for awesomeness", 500
    return jsonify(
        language=language,
        features=random.sample(features, size)
    )

app.run(debug=True)

启动flask后,访问http://127.0.0.1:5000/apidocs

 

 

我们可以看到这就是我们在测试服务器的用文档制定的契约,我们定义了3个接口,其中多出来的一个是post请求,有点restful风格,这里的接口都是可以点击展开的,点开后如下图

 

 

版权声明:本文原创发表于 博客园,作者为 RainBol 本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。

posted @ 2020-03-09 15:08  RainBol  阅读(318)  评论(0编辑  收藏  举报
Live2D