python expect模块_微服务下的契约测试—Python版

d71e397389d7a586af9c3981c2bb1733.png

契约测试的背景

随着敏捷流程和DevOps的盛行,大项目的发版上线流程变得越来越笨重,在这种要求快速发布快速迭代的项目里,微服务的优势凸显无疑。 一个大项目按照功能或者分类等某一类共性拆成多个子组件,每个组件独立维护、测试、发布,敏捷流程容易推动,开发、测试、产品的工作也相对轻松。原本可能一个月只能发2个版本的产品,在微服务架构下可能发N个版本。

微服务架构下的困境

微服务架构固然有自己天然的优势,不过微服务架构特点,也给开发测试带来了不小的挑战: - 第一,维护成本的提升。原来3个大的系统,现在变成了30个功能代码组件,每个组件需要专人维护,需要提供稳定测试环境,关联查找和分析是一件麻烦事; - 第二,系统复杂性的提升。接上,彼此之间的调用关系指数级上升,原先一个接口的调用可能只会涉及到2个系统,在微服务的架构下,会调用10+的组件,复杂性较高。基于此,想要维护一套完整的、不受干扰的测试环境很难实现

传统接口测试的局限性

大部分场景下面,我们通常依靠接口测试来测试系统级的接口功能正确性,但在微服务背景下,接口测试有较大的局限性。

一,需要一套完整的测试环境;

二,接口测试用例的冗余度指数级增加,原本大模块设计出来的用例,在微服务下面需要针对每个组件都得单独设计测试用例,设计、维护、调用成本都会变得很高;

三,测试用例设计要求高,理论上用例需要覆盖到存在调用关系的所有逻辑分支,这需要你对组件调用关系有很清晰的了解。

契约测试

为了解决传统接口测试在微服务结构下的局限性,引入契约测试是很有必要的。

契约测试,Consumer Driven Contracts testing,消费者驱动测试。

在契约测试的理念下,只有两个对象:生产者和消费者,也就是调用者和被调用者,即细化到最小的一个系统组件。契约测试最精髓的思想是测试左移,测试只需要提供组件级的测试用例即可,执行全部交给开发和CI即可。 契约测试可靠吗?是可靠地,前提是大家在契约测试上达成共识,即无论微服务下面有多少组件,只要每个组件完成各自的契约测试,我们就有理由相信,组件集成之后的服务的质量是可靠的。

实例说明

我们用实例更好的理解一下契约测试

  • 1、有一个获取用户信息的接口 /user,参数name,调用地址:http://0.0.0.0:5000/user?name=zhoujielun,返回对应的个人信息普通的接口测试,从客户端出发,通过设计不同的参数组合尽可能的覆盖更多的逻辑分支,比如name=zhoujielun,name=123等等,为了说明契约测试,我们尽量简单说明使用unittest单测框架来做接口测试,我们关注接口的返回代码如 user_unittest.py所示
def test_lizeyang(self):
    """name=lizeyang的测试用例"""
    expect = { "age": 21, "home": "china"}
    res = requests.get("http://127.0.0.1:5000/user?name=lizeyang").json()
    self.assertEqual(res, expect)
def test_zhoujielun(self):
    """name=zhoujielun的测试用例"""
    expect = { "age": 21, "home": "america"}
    res = requests.get("http://127.0.0.1:5000/user?name=zhoujielun").json()
    self.assertEqual(res, expect)
    self.assertEqual(res, expect)

运行python user_unittest.py,结果如下:

======================================================================
FAIL: test_lizeyang (__main__.UserTesting)
name=lizeyang的测试用例
----------------------------------------------------------------------
Traceback (most recent call last):
  File "user_unittest.py", line 19, in test_lizeyang
    self.assertEqual(res, expect)
AssertionError: {u'errmsg': u'user not exist.'} != {'home': 'china', 'age': 21}
- {u'errmsg': u'user not exist.'}
+ {'age': 21, 'home': 'china'}
----------------------------------------------------------------------
Ran 2 tests in 0.015s
FAILED (failures=1)

可以看到,name=lizeyang的测试用例是失败的,预期结果和实际调用结果不相符

上面是一个简单的接口测试用例,对于普通服务的接口测试来说,这种测试方法是可行的,没有问题但是在微服务架构下,会遇到一个问题:某服务X改了接口返回的数据或者结构,比如原先的返回code字段变更成了status或者某个home字段的值,按照上面的测试方法,我们独立对X测试的时候,优先考虑的是X服务的可用性和正确性,很难去实际度量X的上游调用方他的代码在实际调用X接口的时候是否正常,服务他的预期,基于此,微服务的一个组件,他的上游组件数量是N多。

生产者按照自己的意愿去生产接口,导致这些接口无法满足消费者的需求,从而引发各种开发、测试问题,显然不是我们想要的结果。契约测试的出现就是为了解决这种需求与实际不对等的问题,本质上其实是一个基于mock服务的接口测试

通俗的契约测试核心思想:对于任意两个存在调用关系的服务组件A和B,A需要调用B的接口api_01,从契约的角度出发,我们需要从A这个消费者的角度制造一个契约(或者叫合同吧),合同里面约定了接口、接口入参以及在入参的前提接口应该给出什么样的返回。然后B组件的接口api_01在开发的时候需要按照契约的要求去开发,开发完成之后由B端人员发起契约测试或者CI集成测试

契约测试 = 测试左移 + mock + 接口测试

#!/usr/bin/python
# -*- encoding:utf-8 -*-
import atexit
import requests
import unittest
from pact import EachLike, SomethingLike, Term
from pact.consumer import Consumer
from pact.provider import Provider
# 定义一个pact,消费者是ModuleA,生产者是ModuleB,契约文件存放在pacts文件夹下
pact = Consumer('ModuleA').has_pact_with(Provider('ModuleB'), pact_dir='./pacts’)
# 启动服务
pact.start_service()
atexit.register(pact.stop_service)

class UserTesting(unittest.TestCase):
    def runTest(self):
        self.test_lizeyang()
    def test_lizeyang(self):
        # 预期结果
        expected = {"age": 222, "home": "china"}
        # 契约的实际内容
        (pact
         .given('test lizeyang this user.')
         .upon_receiving('a request for the user `lizeyang`')
         .with_request('get', '/user', query={"name": "lizeyang"})
         .will_respond_with(200, body=expected))
        # 调用pact自带的mock服务,注册接口
        with pact:
            res = requests.get("http://localhost:1234/user?name=lizeyang").json()
        self.assertEqual(res, expected)

if __name__ == "__main__":
    ut = UserTesting()
    ut.test_lizeyang()

运行python user_pact_test.py之后,

INFO  WEBrick 1.3.1
INFO  ruby 2.2.2 (2015-04-13) [x86_64-darwin13]
INFO  WEBrick::HTTPServer#start: pid=63533 port=1234
{u'home': u'china', u'age': 222}
INFO  going to shutdown ...
INFO  WEBrick::HTTPServer#start done.

这时,在contracts/pacts文件夹下会自动生产一个契约文件modulea-moduleb.json,内容如下。

{
  "consumer": {
    "name": "ModuleA"
  },
  "provider": {
    "name": "ModuleB"
  },
  "interactions": [
    {
      "description": "a request for the user `lizeyang`”,  # 描述
      "providerState": "test lizeyang this user.”,
      "request": {
        "method": "get",
        "path": "/user",
        "query": "name=lizeyang"
      },
      "response": {
        "status": 200,
        "headers": {
        },
        "body": {
          "home": "china",
          "age": 222
        }
      }
    }
  ],
  "metadata": {
    "pactSpecification": {
      "version": "2.0.0"
    }
  }
}

这个契约文件定义了调用生产者ModuleB时候的测试用例和返回报文的格式 B端拿到这份契约之后,需要按照个契约的内容完成接口开发以及自测 自测方法:

pact-verifier --provider-base-url=http://127.0.0.1:5000 --pact-url=./contracts/pacts/test_sender-service_001.json

4037a17f60aa2f7c1d3a9a2ae61949b8.png

可以看到自测接口与实际的返回是不一致的,开发需要按照契约修改自己的代码来通过契约测试即可。 至此,这个Python的契约测试实例就完成了,当然了契约测试的内容不仅仅只有这样,还包括契约测试的数据文件和版本管理,这个后面有机会再单独讲讲吧。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值