news 2026/5/8 5:25:31

没有契约测试的微服务是什么样的?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
没有契约测试的微服务是什么样的?

01.微服务为什么需要契约测试

先我介绍一下公司的情况。我们使用的是微服务架构,每个部分会负责其中的几个微服务的研发和维护。我所在的部门维护公司的支付服务(billing),这个服务需要依赖其他部门的几个服务。

当用户需要支付一笔订单时,会调用 billing 服务,同时携带很多参数,为了方便,我们先只考虑核心的两个参数:用户 id 和支付金额。

当 billing 接收到用户请求时,会调用其他的依赖服务,用户服务(user)是其中的一个。我们需要查询该用户是否有足够多的余额可以支付订单。

  1. def pay(uid, amount):

  2. """付款"""

  3. user_host = app.config.get("user_server_host")

  4. user_server = f'{user_host}/user/{uid}/pay'

  5. resp = httpx.get(user_server)

  6. user = resp.json()

  7. if user.get('balance') < amount:

  8. return {"msg": "not enough money"}

  9. return {"msg": "success", "amount": amount, "uid": uid}

user 被很多服务调用,而 billing 主要想消费它的 balance 字段校验余额是否足够。user 判断如果是 billing 请求的 /user/<uid> 接口,则把余额给它。其他的消费者请求时返回的数据不太一样。 ​​​​​

  1. def user_info(uid, src):

  2. """用户信息"""

  3. if src == 'pay':

  4. return {"id": uid,"balance": 8000}

  5. elif src == 'manage':

  6. return { "id": uid, "age": 19}

  7. ...

某一天用户直接反馈支付出错,但是我们最近都没有对 billing 服务进行任何的修改,显然,这个突发情况可能不是由我们部门引起的。

我们只能紧急的去线上排查问题,通过一系列分析,终于发现是 user 服务返回的余额字段由 balance 改成了 user_balance,其他没有发生任何变化。user 服务修改了代码,却没有通知我们。

数据结构的轻微变化导致我们根本拿不到 balance 字段,所以我们的支付服务无法继续。

于是我只能立马给 user 部门发邮件。在微服务的维护过程中,最简单有效的测试工具是邮件。因为一个服务往往依赖于其他多个服务,又被另外的服务依赖,导致最后的数据流向变得跟蜘蛛网一样。可是当涉及到跨部门协作的时候,沟通起来也并不那么容易。

我们来看一下这么简单的一处改动会涉及到的流程。首先,user 服务收到需求更改,修改代码之后,user 的测试人员会对自己服务进行组件测试,但是 billing 调用 user 的集成测试他们没有办法测,因为他们根本不知道 billing 是怎么消费它提供的数据的。

user 决定通知所有的下游服务,凡是使用了我这个服务,这个接口的,通通发一遍邮件,麻烦你们各自部门测试一下你们的业务,我的 /user/<id> 接口进行了修改,可能会引发你们的服务异常。其他部门收到通知以后,再针对性的测试这个接口。

这些下游部门会因为一处小改动,浪费非常多的时间和资源。为什么呢?billing 服务是受到修改影响的,所以对我们来说是必要的。但是其他的服务虽然调用了 /user/<id> 这个接口,却并没有消费 balance 这个字段,balance 字段的改动对他们不会产生任何影响。但是他们在测试之前是不知道会不会有影响的,是不是浪费时间呢?

还有一个问题,billing 部门难道不可以建立对 user 服务的集成测试吗?当然可以,实际上我们有专门 mock user 服务的测试,也有对 user 的集成测试,偶尔也会进行手工测试。

但是 mock 测试、集成测试和手工测试这些测试手段都不能及时发现问题。

首先我们看看 mock 测试。mock 服务器是由我们自己掌控的,他的结果是不可信赖的,并不能完全代替真实服务,我们之所以用 mock,是因为快、可控、稳定性高,而且能做到环境隔离。

当真实的 user 服务发生变动时,mock 的数据格式并不能及时更新,所以这些测试用例并不会爆红,而是继续通过。

而集成测试和手工测试都有相同的缺陷,他们太慢了。billing 部门的整套集成测试从构建到结束需要 5 个小时,手工测试一轮就更久了。也就是说,我们至少需要 5 个小时才能发现我们依赖的服务发生了变化,而这个问题早就引发了我们的系统瘫痪长达 5 个小时之久。

契约测试是融合了 mock 测试和集成测试的综合性测试手段,他把消费者 billing 编写的测试用例形成契约文件(contract file),放到提供者 user 端去构建。

当代码修改以后,提供者 user 先针对所有的契约文件构建一次测试,如果所有的契约文件都满足,没有造成毁约现象,就可以上线了。如果有毁约,则需要和被毁约方协商解决,再上线。

附消费者和提供者示例代码,以及 mock 测试和集成测试示例代码:

billing server:

  1. import httpx

  2. from flask import (Flask,request)

  3. app = Flask(__name__)

  4. app.config.update(user_server_host='<http://localhost:5001>')

  5. @app.route('/pay/<uid>/<int:amount>')

  6. def pay(uid, amount):

  7. """付款"""

  8. user_host = app.config.get("user_server_host")

  9. user_server = f'{user_host}/user/{uid}/pay'

  10. resp = httpx.get(user_server)

  11. user = resp.json()

  12. if user.get('balance') < amount:

  13. return {"msg": "not enough money"}

  14. return {"msg": "success","amount": amount, "uid": uid }

  15. if __name__ == '__main__':

  16. app.run(port=5000, debug=True)

user_server:

  1. from flask import (Flask, request)

  2. app = Flask(__name__)

  3. @app.route('/user/<uid>/<src>')

  4. def user_info(uid, src):

  5. """付款"""

  6. if src == 'pay':

  7. return {"id": uid, "balance": 8000}

  8. elif src == 'manage':

  9. return {"id": uid, "age": 19}

  10. ...

  11. if __name__ == '__main__':

  12. app.run(port=5001)

测试代码:

  1. import unittest

  2. from billing_server import app

  3. class TestBilling(unittest.TestCase):

  4. def test_mock_user_server(self):

  5. app.config.update(user_server_host='<http://localhost:5002>')

  6. with app.test_client() as client:

  7. resp = client.get('/pay/1/100')

  8. assert b"amount" in resp.data

  9. def test_real_user_server(self):

  10. app.config.update(user_server_host='<http://localhost:5001>')

  11. with app.test_client() as client:

  12. resp = client.get('/pay/1/100')

  13. assert b"amount" in resp.data

  14. if __name__ == '__main__':

  15. unittest.main()

真实的 user 服务更新后,bill 服务端的测试用例如果 mock 了依赖的 user 服务,测试用例会继续通过,因为 mock 服务是不可信赖的。

如果使用集成测试方法,直接调用远端的服务,不可避免的会造成测试运行很慢,如果整套测试运行需要 2 个小时,则会造成用户无法使用 2 个小时后,才能发现问题。

感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/21 8:42:54

Huggingface 214页训练手册:揭露构建世界级大模型的秘密

《The Smol Training Playbook: The Secrets to Building World-Class LLMs》由 Hugging Face 团队于 2025 年 10 月 30 日发布&#xff0c;详情见https://huggingface.co/spaces/HuggingFaceTB/smol-training-playbook&#xff0c;聚焦SmolLM3&#xff08;3B 参数多语言推理模…

作者头像 李华
网站建设 2026/5/8 5:20:24

学长亲荐8个AI论文软件,助你搞定本科生毕业论文!

学长亲荐8个AI论文软件&#xff0c;助你搞定本科生毕业论文&#xff01; AI 工具如何成为论文写作的得力助手 对于大多数本科生而言&#xff0c;撰写毕业论文是一项既紧张又充满挑战的任务。从选题、开题到资料收集、撰写初稿&#xff0c;再到修改润色和查重降重&#xff0c;…

作者头像 李华
网站建设 2026/4/30 5:00:02

微电网优化调度:多目标与改进粒子群算法的魅力

微电网优化调度作为智能电网优化的重要组成部分&#xff0c;对降低能耗、环境污染具有重要 意义。 微电网的发展目标既要满足电力供应的基本需求&#xff0c;又要提高经济效益和环境保护。 对此&#xff0c; 提出了一种综合考虑微电网系统运行成本和环境保护成本的并网模式下微…

作者头像 李华
网站建设 2026/5/1 5:16:16

PyTorch模型转TensorFlow全流程实操记录

PyTorch模型转TensorFlow全流程实操记录 在深度学习项目从实验走向落地的过程中&#xff0c;一个常见的现实挑战悄然浮现&#xff1a;研究团队用 PyTorch 快速验证了某个高精度模型&#xff0c;而工程团队却被告知——“请把它部署到生产环境”。问题来了&#xff1a;我们的服务…

作者头像 李华
网站建设 2026/5/1 10:20:05

Apple Silicon M系列芯片上的TensorFlow性能表现

Apple Silicon M系列芯片上的TensorFlow性能表现 在人工智能开发日益向边缘和终端设备迁移的今天&#xff0c;越来越多的数据科学家开始思考一个问题&#xff1a;是否可以在不依赖云端GPU集群的情况下&#xff0c;在自己的MacBook上高效训练一个中等规模的深度学习模型&#xf…

作者头像 李华
网站建设 2026/5/2 17:24:54

FPGA加速TensorFlow推理:Xilinx Alveo实测

FPGA加速TensorFlow推理&#xff1a;Xilinx Alveo实测 在AI模型日益复杂、部署场景愈发严苛的今天&#xff0c;推理性能早已不再只是“跑得快”的问题&#xff0c;而是关乎系统响应能力、能效成本和运维可持续性的综合挑战。尤其是在金融交易风控、工业质检流水线或城市级视频分…

作者头像 李华