微服务 python 下载_微服务下的契约测试—Python版

契约测试的背景

随着敏捷流程和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

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值