译者 | 许学文
策划 | Tina
在推动功能交付的全生命周期中,我们团队严格遵循一套敏捷实践。首先,我们采用数据驱动的方法来识别和提出潜在的功能点或改进点,然后我们以接收文档的形式提出建议。一旦获批,我们便开始细化需求,界定可交付成果的范围。接下来,我们会专注于迭代开发,通过设定一系列便于管理的里程碑,逐步实现这项功能。在技术文档、初始设计模型、 API Schema 和工单创建等准备工作完成后,我们便开始真正地实现这个功能。
然而,在开发过程中,我们常常会遇到的一个情况是:因为 API 端点的开发尚未完成,所以前端开发人员往往无法从真实的 API 端点获取所需的数据,只能转而依赖静态模拟的 API 响应来继续 UI 开发工作。为了解决这一问题,市场上出现了 mirage.js 、Mock Service Worker (MSW) 等流行的工具。这些工具通过拦截对真实后端的请求并返回预设的响应数据,有效地模拟了服务器的行为。这种方法使得前端开发人员可以独立于后端开展开发工作,从而加速开发流程,并缩短实现里程碑目标所需的时间。借助这些工具,前端开发的进度可以不受后端开发进度的限制,提高了整个开发团队的效率。
尽管使用静态模拟 API 响应的方法可以解决前端开发对于后端的依赖问题,但在产品验证阶段,它可能带来新的挑战。通常,产品验证流程会按照以下步骤进行:开发人员首先将功能的最新版本部署到一个准生产环境中,这个环境只对获得授权的用户开放,而不对外部用户开放。然后,这些内部用户就可以对该功能进行初步验证,但仅限于模拟数据所能展示的状态。很自然地,为了更全面地验证功能,他们可能会发送一些特殊的请求,看看当 API 响应返回某些临界值时,该功能的表现如何。为了满足这些要求,开发人员需要对代码做相应的调整,提交一个新的合并请求,并在审核通过后再次部署到准生产环境中。这一过程影响了验证的效率,我们可以进一步优化这些步骤,使内部用户在验证功能时能够更加独立,减少对开发人员的依赖。通过改进代码管理和部署流程,我们可以确保产品验证环节更加流畅,从而缩短产品从开发到发布的整个周期。
解决方案总结
虽然我们选择的解决方案主要依赖于 mirage.js,但也可以采用其他类似的服务器模拟库。在我们的案例中,在初步研究了 mirage.js 的适用性之后,我们觉得几乎没有理由再去尝试其他的库了,因为它已经满足了我们的需求。然而,我们面临的一个挑战是,对于一个端点,这些库通常只能模拟一个响应。如果我们需要根据不同的测试场景加载不同的模拟响应,就必须对代码进行修改。
为了克服这一挑战,我们在 mirage.js 的基础上开发了一个用户界面(UI),允许用户选择每个 API 端点应该返回的响应类型,从而控制应用程序以期望的方式运行。例如,我们的数据新鲜度功能会根据关键绩效指标(KPI)或其他类似数据的最新更新做不同的展示。如果产品经理需要验证该功能在相应端点返回新数据、过期数据或无数据时的外观变化,那么他们只需通过模拟服务器的 UI 界面选择相应的选项。选择完成后,相应的变化将立即生效,无需进行代码修改或其他复杂的操作。
这样一来,用户就能够自主检查特定的 UI 边缘案例和场景,而无需依赖开发人员的介入或等待重新部署准生产环境。此外,一旦后端完成了真正的 API 端点,我们可以立即关闭模拟服务器。为了实现这一功能,我们只需额外增加一个步骤,就是针对该功能可能呈现的各种边缘情况为模拟服务器配置多个模拟数据集,从而确保用户能够全面测试它在不同情况下的表现。
深入探讨
模拟服务器的构建遵循了 mirage.js 官方文档中的指导原则,我们需要定义以下三个部分:
Provider 组件: 为了确保模拟服务器能够有效地拦截所有相关端点,它应该在应用程序的关键部分被加载之前实例化。在此之后,模拟服务器的每个端点可能只会返回一个响应。为了消除这个限制,用户界面(UI)允许用户控制模拟服务器的实例化时间,以根据用户偏好加载不同的模拟响应。这是通过使用一个封装组件(如 React 的 Context API)实现的,它不仅包含了重新实例化的逻辑,而且还简化了模拟服务器的设置。通过使用 Context API 封装主组件,开发人员可以通过向 Provider 组件提供必要的 props 来轻松地配置模拟服务器。这种方法简化了 UI 组件()的实现过程,并且使它可以自动收集所有需要的信息,而不需要额外的 props。
会话存储(Session Storage): 我们面临的另一项挑战是向端点传递多样化的模拟响应。我们的系统允许用户在应用程序生命周期的任何阶段,通过用户界面(UI)选项更改服务端返回的响应,这个过程需要重新加载一次页面,以便模拟服务器能够加载一套不同的模拟数据集。然而,因为在页面重新加载的过程中,整个应用程序将经历重新挂载的过程,所以无法通过应用程序的状态管理机制来保留用户之前选择的设置。为了解决这个问题,我们采用了浏览器的会话存储功能,以便在应用程序生命周期之外持久化用户状态。当会话结束时,我们会清理会话存储对象中的条目以释放资源。与此同时,为了避免在同一个会话中运行多个应用程序实例时使用模拟服务器可能导致的数据冲突,我们还引入了一种唯一键的机制。这个机制确保了不同应用程序实例之间的模拟状态是相互独立的,从而防止了潜在的数据混淆。
UI 界面使用了 UI-Kit 库中的一系列组件。该库的主要目的是为了快速完成核心功能的开发。它使用户能够方便地按需进行响应模拟,执行页面刷新,以及控制模拟服务器的启用或禁用。
限制和替代
基于 mirage.js 实现模拟服务器,我们不仅获得了应用程序 API 和用户界面并行开发的固有优势,而且还提高了灵活性和可访问性:
该解决方案并不能取代针对边缘场景编写的单元测试。在开发过程中,单元测试始还是要优先考虑的,而模拟服务器只是对应用程序开发过程的一个有益补充。它简化了边缘场景的展示,特别是在演示环节中。契约测试旨在测试 API 提供者和客户端之间的交互,以确保请求能够被正确地理解,并生成恰当的响应,因此也是需要优先考虑的。模拟服务器的真正优势体现在 API 服务尚处于开发阶段时,它可以作为一个有效的临时解决方案,使开发者可以在服务完全可用之前,对前端进行测试和验证,从而确保开发过程的连续性和效率。
尽管这一特定的实现是专为针对 REST API 的,但该方法应该能够兼容 GraphQL 架构,例如 Apollo 框架所提供的架构,它已经内建了自己的模拟服务解决方案。然而,无论采用何种技术,模拟的定义完全是在前端完成的,也就是说,将常规的 API 验证和错误处理与任何后端服务分隔开来。在这个过程中要特别注意,务必确保模拟服务与原本计划模拟的后端服务 Schema 一致。
总 结
总体而言,得益于产品经理和设计师的积极反馈,我们将模拟服务器集成到了我们的应用程序中。模拟服务器让产品经理和设计师可以在开发的各个阶段便捷地查看功能,促进了他们与工程师之间的协作。同时,它通过封装非业务逻辑及提供直观的组件,简化了工程师部署模拟服务器解决方案的过程。经过几轮实施之后,我们开发出了一个更加通用的模拟服务器版本。现在,我们已经将其作为一个独立的 NPM 模块在公司内部使用。
最后,尽管这一解决方案可能并不适合所有的场景,但我们还是要强调一下,开发人员在团队中要有必要的空间、资源和支持,要鼓励他们以多样化的方式进行探索和实验。这至关重要。这样的环境能够确保创新思想有足够的时间和条件逐渐成熟,最终结出丰硕的果实。
原文地址:
https://engineering.zalando.com/posts/2024/04/enhancing-the-mock-server-a-ui-approach.html
声明:本文由 InfoQ 翻译,未经许可禁止转载。