♻️
前端 TDD(测试驱动开发)
  • README
  • 总-课程目标
  • 总-课程安排
  • Coding
    • 第一题:FizzBuzz 数字游戏
      • 什么是 FizzBuzz?
      • 任务 0:练功前的热身
      • FizzBuzz 项目剖析
      • 任务 1:TDD 初体验
      • 任务 2:先利其器
      • 任务 3:再撸一遍
      • FizzBuzz 项目总结
      • 附录 1:Jest 测试基础及要点
      • 附录 2:Testing Library 组件测试基础
    • 第二题:MarsRover 火星车
      • 任务 1:Tasking 任务分解
      • 火星车实战
      • 火星车 Tasking 任务分解
      • 任务 2:MarsRover 实战演练
      • MarsRover Coding 演示
      • 任务 3:MarsRover 练习
      • MarsRover 项目总结
    • 第三题:CommentBox 留言板
      • CommentBox 项目剖析
      • 任务 1:Cypress E2E 测试
      • 任务 2:测试驱动组件单元接口
      • 任务 3:组件级别的快速反馈
      • 任务 4:由外到内的前端 TDD
      • CommentBox 项目总结
    • 第四题:Bookshelf 魔法书架
      • Bookshelf 项目剖析
      • 任务 1:练习 API 契约测试
      • 任务 2:组件化与数据流管理
      • 任务 3:Redux 数据流测试
      • 任务 4:简化 Redux 项目结构
      • Bookshelf 项目总结
      • 附录 1:什么才是真正的 RESTful 架构?
      • 附录 2:【译】Redux 和 命令模式
      • 附录 3:【译】什么是 Flux 架构?(兼谈 DDD 和 CQRS)
    • 第五题:ShoppingCart 购物车
      • ShoppingCart 项目剖析
      • React 哲学:Thinking in React
      • 任务 1:任务分解 - 驱动组件树拆分
      • 任务 2:综合应用 - 驱动组件接口设计
      • 任务 3:综合应用 - 驱动数据流管理
      • 任务 4:综合应用 - 驱动组件样式开发
      • ShoppingCart 项目总结
由 GitBook 提供支持
在本页
  • 什么是契约测试?
  • 练习目标
  • 任务分解(API Tasking)
  • 编写契约测试(Contract Testing)
  • 启动 Mock Server
  • 本次任务的练习要求
  • 也请你思考一下
  • 契约测试与其他测试的关系?
  • 在 E2E 层面如何使用 Mock Server?
  • 从协同的角度来说,GraphQL 的价值是什么?
  • 阅读材料
  1. Coding
  2. 第四题:Bookshelf 魔法书架

任务 1:练习 API 契约测试

上一页Bookshelf 项目剖析下一页任务 2:组件化与数据流管理

最后更新于5年前

Hey,你好,我是 JimmyLv 吕靖,这是你开始 前端 TDD 练习的第 15 天。今天你将会开始练习 Bookshelf 项目,我们会从一个简单的 RESTful API 入手,使用契约驱动开发的方式模拟和验证前后端协作。

在团队内部,前后端协作本质上需要的不是一份 API 文档,而是一个可以供前后端共同遵守的契约。前后端开发人员可以一起制定一份契约,使用这份契约共同开发,前端使用这份契约启动 mock API,后端则可以通过它简单的验证 API 是否正确输出想要的数据。

什么是契约测试?

契约测试本质上也是一种 TDD,契约驱动开发,双方先定契约,你做你的 我做我的,契约可以自动化执行,检测双方是否达成契约要求。

契约可以理解为反向的录像机(VCR)。 VCR 记录实际的提供者行为,并验证使用者的行为是否符合预期。 Pact 记录消费者行为,并验证提供者的行为是否符合预期。

git clone https://github.com/JimmyLv/tdd-bookshelf.git
cd tdd-bookshelf

yarn install #安装依赖
yarn start #启动应用

练习目标

任务分解(API Tasking)

举个例子,我们需要实现:

  • GET /api/books - Retrieve all books (with query?)

  • GET /api/books/{id} - Retrieve a single book detail by ID

编写契约测试(Contract Testing)

yarn pact:test #运行 pact 测试
test('getting all books', async () => {
  // Given: set up Pact interactions
  await provider.addInteraction({
    state: 'books exist',
    uponReceiving: 'get all books',
    withRequest: {
      method: 'GET',
      path: '/api/books',
    },
    willRespondWith: {
      status: 200,
      headers: {
        'Content-Type': 'application/json; charset=utf-8',
      },
      body: like([somethingLike(bookContent), somethingLike(anotherBookContent)]),
    },
  })

  // When: make request to Pact mock server
  const response = await API.getAllBooks()

  // Then: verify the response data and status
  expect(response.data).toStrictEqual([bookContent, anotherBookContent])
  expect(response.status).toEqual(200)
})

启动 Mock Server

由于我们没有真正去实现一个 Provider 来提供 API,所以从前端的角度我们期望方便快捷得启动一个 Mocker Server。

yarn pact:stubs #启动 Pact Stub server
const pact = require('@pact-foundation/pact-node')
const path = require('path')

const PACT_FILE = path.resolve(__dirname, '../tests/pacts/contracts/bookshelf-bookapi.json')

const server = pact.createStub({
  pactUrls: [PACT_FILE],
  port: 8080,
  cors: true,
})
server.start().then(function () {
  console.info(`Mock server running on http://localhost:${8080}/api`)
  // Do your testing/development here
})

在我们平常的前端开发当中也会用到 mock server,现在让我们把这个 mock server 改造一下,引入测试先行的概念,变成即可以提供 mock 数据,又可以作为契约自动化验证后端 real API,这样一举两得了。

参考资料:

本次任务的练习要求

  • 理解契约测试当中的 Consumer 和 Provider,以及 contract 契约文件。

  • 契约先行,通过测试生成 mock server 再进行前端页面的业务开发。

也请你思考一下

契约测试与其他测试的关系?

那么,请你从契约测试的运行速度的角度思考一下,这个 Pact 测试跑起来快嘛?API 测试跟单元测试的运行速度有什么区别?分别有什么目的和侧重点?适用于什么样的场景?

下一步,如果你需要从软件开发的全局上下游考虑问题,你该如何向后端 API 开发人员(如果你们采取前后端分离的开发模式)解释契约测试呢?如何让双方理解前后端契约的价值并在项目中推行呢?

使用 Pact Stub server 的办法直接开启前端开发的进程,摆脱对后端 API 环境未 Ready 的依赖。那么更进一步,是时候将契约测试推广到后端,跟后端开发小伙伴共同约定一份契约,放到 Pact Broker 以及 CI 用来辅助 API 开发吧!

在 E2E 层面如何使用 Mock Server?

cy.server()

cy.fixture('books.json').as('booksJSON')
cy.route('GET', 'books/*', '@booksJSON')

但是,我们要尽可能让前端的 mock server 跟后端的 real server 保持一致,这样在每次后端(Provider) API 变更时都能够测试是否满足前端的需要,又能够在前端(Consumer)的角度检查 API 是否破坏了既有需求,从而保障约定好的契约与实际实现能够正确执行。

理解一下 Functional Testing vs Integration E2E Testing 的区别,前者其实是可以在前端利用 mock server 保障前端的功能不会挂,即 API 数据供给永远都是好的,我们测试的是前端的数据显示与交互功能是否正常如初;而后者则更偏向于端到端的功能集成测试,当整个应用出现错误异常,有可能是前端出现问题,有可能是后端 API 出现问题,也有可能是前后端各自安好,但是对接出现问题。

这时,出现 Bug 时我们就要去定位问题,而我们会发现,问题发现得越晚,其修复成本越高。我们得去逐一排查:前端界面 -> 前端数据流(Store) -> 前后端集成 -> 后端 API 数据 -> 后端数据库,然后去是不是前端有问题,如果前端没问题那就再切换视角到后端,而如果前后端当前的优先级不一致,就会导致各自排查的积极性不足,而导致前端等后端,后端怪前端的尴尬境地。测试驱动开发,让我们可以从契约测试出发,先定义好契约然后用自动化的方式去保障和追踪 API 接口变更是否满足预期,对比一下,再想想你们做前后端联调时的痛苦吧!

从协同的角度来说,GraphQL 的价值是什么?

参考资料:

阅读材料

Pact 测试又名契约测试,是消费者服务与生产者服务之间的消费者驱动测试,消费者驱动的契约测试()。

当然,我还通过 CodeSandbox 的方式作为你的快速入口: (可能需要科学上网),帮助你去除设置开发环境的障碍,一键打开 Cloud IDE 即可开始编码,右边则是你的网站运行效果预览,比如:

Simple Consumer
Sequence Diagram

使用上述的契约文件通过 Pact 启动一个

设计符合 REST 规范的 API,参考文章:

也许你也能够明白这一点,契约测试只是替代了部分的系统集成测试(即确保正确使用 API 以及 API 以期望的方式进行响应)。但是它并,金字塔结构中的其他测试侧重点不同,保障的是应用程序的其他核心业务逻辑能够正常工作。

尝试了解一下 ,有了它之后,后端如果再悄咪咪改 API 字段,公共的 Pact Broker 瞬间就晓得了,契约这一层属于中间层,有点中间人的意思,当然它还可以映射各服务之间的调用关系。

测试覆盖范围对比

其实 Cypress 也可以 ,但是我建议不要在 E2E 框架层面 mock 任何东西,要防止因为 API 层面挂掉,而导致前端无法继续开发,或者持续集成无法进行。

Consumer-Driven Contracts
https://codesandbox.io/s/github/JimmyLv/tdd-bookshelf
https://tdd-bookshelf.jimmylv.now.sh/
Mock Server
Local API stubs - Pact
kununu/pact-cli: A CLI Tool to start an API mock server via pact interaction files.
什么才是真正的 RESTful 架构?
不会取代其他维度的测试
Pact Broker
Stubbing the server
GraphQL | 一种为你的 API 而生的查询语言
Schema-First GraphQL: The Road Less Travelled - Blog | Mirumee
Code-first vs. schema-first development in GraphQL - LogRocket Blog
聊一聊契约测试
Consumer-Driven Contracts: A Service Evolution Pattern
pact-foundation/pact-workshop-js: Pact JS workshop - learn Pact in 60 minutes
Edit tdd-bookshelf
CodeSandbox 代码编辑
API 契约(API Contract)
Generate Pact Contract File
Mock Pact Provider
Before contract tests
After contract tests
Pact Broker
API 少了个字段
GraphQL vs REST
GraphQL