Tubi 前端测试:迁移 Enzyme 到 React Testing Library

news2024/11/27 12:29:28

前端技术发展迅速,即便不说是日新月异,每年也都推出新框架和新技术。Tubi 的产品前端代码仓库始建于 2015 年,至今 8 年有余。可喜的是,多年来紧随 React 社区的发展,Tubi 绝大多数的基础框架选型都遵循了社区流行的最佳实践。核心框架和依赖的版本基本都已经或有计划更新到最新的稳定版本。

能做到这一点,主要得益于 Tubi 小而美的前端团队有着强烈的技术自驱力;此外,团队管理层重视工程师文化和技术基础设施建设,愿意给予团队不少于 20% 的整块时间进行功能开发外的必要技术优化和升级。

本文将介绍的 Enzyme 到 React Testing Library(RTL)的迁移发生在 2022 年底,是 Tubi 前端至关重要且有相当工作量的代码迁移项目之一。

迁移动机

在 Tubi ,即便是社区推荐的技术选型,我们也要先对其必要性和价值做出评估,待达成共识后才会采取进一步的行动。回到 Enzyme 到 RTL 的迁移,主要理由有四个:

第一,Airbnb 官方已经不再活跃维护 Enzyme,且没有计划支持最新的 React 18。Tubi 决定升级到最新的 React 18 ,就必须找到并迁移所有 UI 测试到 Enzyme 的替代方案。

第二,RTL 关注于集成测试的设计理念使得团队可以更轻易、高效地写出易于维护的测试代码。

第三,RTL 鼓励从用户实际使用角度写测试用例,因此其 API 设计理念无形中就引入很多 UI 测试的最佳实践,例如对 Web Accessibility 的关注和强调。

第四,RTL 成熟活跃的社区及轻量的实现机制保证了该测试框架可预期的长久生命力。

关于第二点,相对于大家所熟知的测试金字塔模型,RTL 的作者 Kent C. Dods 提出了与之相对应的测试奖杯模型(Test Trophy),如下图所示。Kent 认为,应该把主要精力放到写 UI 集成测试(Integration Test)上,这样模拟 API 调用和返回结果去整合应用,既可以规避因调用异步 API 导致过慢的测试运行速度,又能从用户实际使用角度全方位测试应用的功能,做到事半功倍。

test trophy.jpg

图片来源:testingjavascript.com/

举例说明,当我们测试页面渲染时,不仅测试了预期被渲染的 UI 元素,同时自然而然地覆盖了该页面数据加载、UI 元素展示和潜在的用户交互及权限控制。换句话说,我们通过 UI 测试用例,就可以顺带自然而然地触及更底层的有关 Redux 状态管理、事件派发和数据组织的功能和逻辑,从而轻松覆盖原本需要单元测试去检测的代码功能和分支条件。因此,UI 集成测试兼顾了测试覆盖度和测试运行速度,是更有效、值得推荐的书写前端测试代码的方式。

第三点中提到的 RTL 先进测试理念在下文 Counter 测试的示例中展示无遗。Enzyme 测试一般会与组件的具体实现细节相绑定。

在示例中,Enzyme 通过 button 对应的 increment class name 定位到自增按钮,并通过检查保存在组件内部 state 中 count 的变化来确定自增计数功能被正确执行。而 RTL 通过 getByLabelTextgetByRole 这样的语义化 API 获取对应遵循 Web Accessibility 规范的元素,进而通过检测用户可见界面的改变而验证计数器自增功能被如预期执行。因此,RTL 测试有两个显而易见的好处:

1. 与 UI 实现细节的解耦让测试代码更加健壮,今后实现细节的变更并不会导致测试代码失效;

2. Web Accessibility 导向的 API 设计鼓励开发者在实现 UI 组件时遵循 Web Accessibility 规范。

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { shallow } from 'enzyme';
import React from 'react';

import Counter from './Counter';

describe('Enzyme tests', () => {
  it('should increment by 1', () => {
    const wrapper = shallow(<Counter />);
    const instance = wrapper.instance();
    expect(instance.state.count).toBe(0);
    wrapper.find('button.increment').simulate('click');
    expect(instance.state.count).toBe(1);
  });
});

describe('RTL tests', () => {
  it('should increment by 1', () => {
    render(<Counter />);
    const countLabel = screen.getByLabelText('count');
    expect(countLabel).toHaveTextContent(0);
    userEvent.click(screen.getByRole('button', { name: 'Increment' }));
    expect(countLabel).toHaveTextContent(1);
  });
});

作者:隔壁正在装修真羡慕
链接:https://juejin.cn/post/7260024054066085946
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

此外,RTL 和 Enzyme 的 npm 下载次数统计也清晰反映出当前 RTL 已经取代 Enzyme,成为 React 前端项目测试框架的不二选择。因此,我们将 Enzyme 到 RTL 的迁移工作提上了正式日程。

RTL vs Enzyme.jpg

迁移规划

了解迁移项目规模和整体概况对于我们制定切实可行的迁移规划和策略至关重要。Tubi 产品前端代码仓库有着高达 92% 的代码测试覆盖率。依照迁移项目启动前的统计,我们依赖 Enzyme 的 UI 测试文件总共有 440+ 个(涉及的总代码行数应该超过 10 万行)。如果迁移不能很快完成,随着新 Enzyme 测试的创建,这个工作量还会持续增加。

因此,我们新功能和新组件的开发需要尽早强制使用 RTL 去测试;同时,对于存量 Enzyme 测试,我们需要找到一个渐进的方案实现逐步迁移。一个分而治之的迁移路径由此浮现,如下图所示。

mindmap.jpg

验证可行性

为了进一步验证 RTL 是否可以匹配我们对新功能新组建的 UI 测试需求,我们决定从实战出发,将 RTL 应用到我们当时正在开发的世界杯活动页面上。选用这个页面做验证,是本着先难后易的原则。因为这个着陆页足够复杂,涉及响应式 UI 、deep links 调用、API mocks 、用户交互和页面跳转等众多复杂场景的测试。我们认为,如果 RTL 可以很好地满足在复杂场景下的测试,那么相对简单的 UI 测试就更不成问题了。

最终的结果令人满意,RTL 可以完美适配上述测试场景。相对于 Enzyme 测试,RTL 测试的实现更加简洁高效,且测试运行时间并没有显著改变。同时,我们还封装了适配 Redux、react-intl 等第三方库初始化的自定义渲染(render)方法,并沿用 Nock 在 API 层面去模拟 API 的响应结果。这一切,都让我们得以在尽量维持技术选型和依赖不变的基础上,做到相对严格地遵循 RTL 官方推荐实践和理念。这些成功而积极的正向反馈坚定了团队向 RTL 测试迁移的信心。

引入 Lint 和进度统计脚本

延续分而治之的策略,在大规模迁移已有测试前,我们实现了一个自定义的 ESlint 规则并将其添加到 Github CI 中,以确保停止添加新的 Enzyme 测试这一共识被贯彻执行。下图中,Lint 规则的 startDate 被设为 2022-12-16,这意味着在该日期后引入的新 Enzyme 测试会引发 Lint 报错。

lint.jpg

同时,为了方便掌握迁移进度,并快速定位尚未迁移的代码,我们在项目初期就实现了名为 rtl-migrate 的 npm 命令以辅助迁移。如下图所示,运行 yarn run rtl-migrate 可以获知 Enzyme 测试的整体迁移进度和选定文件夹的迁移概要。运行 yarn run rtl-migrate -p src/ott --type=enzyme 将打印出 src/ott 目录下所有尚未迁移的测试文件。此外,该命令还实现了找出测试文件核心贡献者和根据文件大小过滤排序的功能,这些都为我们分配迁移任务给最恰当的开发者提供了数据参考。文章长度所限,这里不一一展示。

 

通过 Codemods 自动迁移

根据最初的分析,我们有不少于 440 个测试文件需要迁移。即便按平均每个文件仅 250 行来做最乐观的估算,整个迁移涉及的测试代码改写量将不少于 11 万行代码。这无疑是一个巨大且可能旷日持久的工程。为了尽可能缩短工期,我们决定基于 jscodeshift 构建自己的 Codemods 脚本,以便尽可能自动迁移典型的 Enzyme 代码模式到 RTL 测试,从而节省人工迁移的成本。

选择自己构建 Codemods 脚本,是因为在开源社区中我们并没有找到符合要求的现成 Enzyme 迁移脚本。同时考虑到我们项目对 Enzyme 的独有封装和扩展,如果希望尽可能灵活精准地将更多 Enzyme 测试自动转化为 RTL 测试,构建可以完全掌控的 Codemods 脚本势在必行。

当然,我们并不期望 Codemods 可以迁移所有测试用例。务实的期望是 Codemods 能以最小代价优先覆盖代码仓库中最常见的测试用例和代码模式。技术选型和决策同样需要衡量投入产出比和最终收益。平衡和取舍往往是技术决策的关键词。尤其对于测试迁移这类一次性的工作而言,实现一个面面俱到的 Codemods 脚本并不是我们的初衷。重点是,我们须确保通过 Codemods 自动迁移带来的时间节省收益大于开发 Codemods 付出的精力消耗。

因此,我们优先找出了 Enzyme 测试中最常见的几种代码模式及其变形,并实现了对应的 Codemods 使其可以被自动转换。这一过程切合我们常说的二八原则,即通过 20% 的成本完成了 80% Codemods 的预定目标,剩余 20% 的功能也许我们可以继续投入 80% 的时间去打磨。考虑到迁移工作一次性投入的特性,对非常见的测试模式我们并没有强求在 Codemods 层级做自动迁移的支持。

回到具体实现,本质上 Codemods 先将特定代码片段转化为抽象语法树,进而识别并修改特定语法树的结构后再重新生成新的代码片段。因此,构建诸如迁移 Enzyme 测试这样功能复杂的 Codemods 时,对 Codemods 功能的拆解和组织尤其重要;否则,复杂性的不断叠加终将导致 Codemods 难以维护。实现之初,我们便仔细设计了 rtl-codemod 代码组织关系和 transformer 间的通信存储机制。具体说明如下图。

rtl-codemod.jpg

这里的关键词是解耦,可以将各 transformer 想象为独立、目的明确的插件或中间件,它们可以互不干扰地独立运行;最终 Codemods 对代码的修改是这些插件按顺序执行的结果。我们设计 rtl-codemod 时,进一步引入了 motions 这个 micro-transformer 的概念,从而构建了两层的插件体系结构,做到了 tranformer 功能的进一步拆解和灵活组装。

作为 Codemods 执行入口的核心,transform 只重点负责配置各 transformers 的执行顺序。其实现大体精简如下:

const transform: Transform = (file, api, options) => {
  const j = api.jscodeshift;
  const { source } = file;
  const ast = j(source);

  // NOTE: The order of motions is important. Some motions need to be
  // applied before others.
  applyMotions(j, ast, [
    ...renderMotions,
    ...snapshotMotions,
    ...findTextMotions,
    ...inTheDocumentMotions,
    ...userEventMotions,
    // More transform-related motions...
  ]);

  return toSource(ast, options.toSourceOptions);
};

此外,作为脚本程序,某 transformer 运行时结果和中间计算产出可以通过持久化存储被其他 tranformers 从特定存储中读取。换言之,存储也可以被理解为 transformer 实现通信和数据共享的媒介。

更多细节可以参考我们开源出来的精简示例实现:github.com/nickqi-tubi…

妥协和适配

做技术决策时,经常会不得不选用一些退而求其次的妥协方案,但这并不完全是一件坏事。相反,为了更好地适配现有代码和模式而主动做出的妥协,往往是一种务实而精明的抉择。

以 Enzyme 迁移为例。

我们在 Enzyme 测试中大量使用了 wrapper.instance() 这种独有 API 去检测组件的期望实例值。这是 Enzyme 测试中常见且官方推荐的模式之一。与之相反,RTL 明确反对针对实现细节设计任何测试用例,这也是 RTL 未曾暴露获取组件实例或内部状态的 API 的原因。

RTL 官方建议

You want your tests to avoid including implementation details so refactors of your components (changes to implementation but not functionality) don't break your tests and slow you and your team down.

RTL 的设计理念和建议无疑是有道理的。但回到测试迁移这个任务上,考虑到我们已经有至少十万行代码基于 Enzyme 实践,去除对 wrapper.instance() 的依赖意味着我们需要彻底重写大量测试,这无疑极大加剧了测试迁移的实现成本而难以推进。务实的考虑是,我们需要为 RTL 提供一种桥接,使 RTL 测试依然可以适配现存针对组件实例和内部状态的测试。

基于上述原因,我们实现了如下所示的 renderWithInstance 工具方法:

function renderWithInstance(
  passedComponentOptions,
  renderOptions
) {
  let incorrectlyUseInstancePattern;
  let WithExtendedClass;
  const { extendingClass } = passedComponentOptions;

  try {
    if (extendingClass.prototype.isReactComponent) {
      WithExtendedClass = class WrapperInstance extends extendingClass {
        constructor(props) {
          super(props);
          incorrectlyUseInstancePattern = this;
        }
      };
    } else {
      WithExtendedClass = class WrapperInstance {
        constructor(_props: any) {
          incorrectlyUseInstancePattern = this;
        }
      };
      Object.setPrototypeOf(WithExtendedClass, extendingClass);
    }
  } catch (e) {
    throw new Error(`Problem extending passed 'extendingClass'.\n${e.stack}`);
  }

  const renderResult = render(
    <WithExtendedClass {...passedComponentOptions.props} />,
    {
      wrapper: getWrapper(renderOptions),
      ...renderOptions,
    }
  );

  return {
    ...renderResult,
    incorrectlyUseInstancePattern,
  };
}

作者:隔壁正在装修真羡慕
链接:https://juejin.cn/post/7260024054066085946
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

通过在迁移后的 RTL 测试中调用 renderWithInstance,我们使自定义的 RTL 渲染方法依然可以暴露组件实例对象而尽量复用已有的检测条件。

it('should call handleUpdateA11y when activeIdx changes', () => {
  const updateA11ySpy = jest.spyOn(
    incorrectlyUseInstancePattern, 'handleUpdateA11y'
  );
  const {
    incorrectlyUseInstancePattern
  } = renderWithInstance({
    extendingClass: AlertModal,
    props: getProps()
  });
  incorrectlyUseInstancePattern.componentDidUpdate(
    {}, { activeIdx: 1 }
  );
  expect(updateA11ySpy).toHaveBeenCalled();
});

作者:隔壁正在装修真羡慕
链接:https://juejin.cn/post/7260024054066085946
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

时间和成本开销

Tubi 前端团队从 Enzyme 到 RTL 的测试迁移工作耗时约两个半月。期间,我们创建并完成了 109 个与之相关的开发任务(stories),共迁移了 466 个测试文件和 10 余万行的测试代码,项目燃尽图如下。

burndown.jpg

其中,绿色表示 Stories 完成的统计出现骤减,是因为在 Shortcut 系统中,只有对应的代码真正发布到生产环境后才被统计为完成;但实际的迁移工作每天都在推进,而非一蹴而就。

通过燃尽图,我们可以看到 Tubi 从 2022 年 9 月已经开始考虑这一迁移工作。当时只是创建了用于概念验证的前期准备工作和团队内部的分享讨论。11 月底,借着圣诞和新年期间线上代码冻结的时机,我们正式将测试迁移排上日程。

2022 年 11 月底到 12 月中旬,我们投入了近一个半的全职前端工程师(两位负责迁移项目的前端工程师,每人投入约 70% 的时间推进迁移工作),构建辅助迁移的 Codemods 、进度报告脚本和 Lint 工具。同时,他们还尝试手动迁移代码库中的一些典型测试,以便了解迁移的难点并找到对应的解决方案。

2022 年 12 月底,随着辅助工具的完善和对迁移难度的掌控,我们对其他长期工作于 Tubi 产品前端代码仓库的工程师进行了 RTL 测试和迁移方面的培训。之后,将需要迁移的测试文件按对所涉及代码的熟悉程度和工作量进行了统一分配。2023 年 2 月中旬,我们实际已经完成了全部的迁移工作,燃尽图在 2 月底上线时将已经合并的测试代码记为完成。

经验和总结

大多数工程师(尤其是前端工程师)希望紧追技术潮流,使用最新最酷的技术,但对于已有代码仓库而言,框架和依赖迁移的成本不容忽视。成熟的工程团队需要平衡利弊、明确投入产出比和必要性后做出理智的决策。推进迁移时,寻求渐进而为的方式应被视为基本策略。当然,对新旧代码分而治之的差异化对待经常是为了快速推进而做出的必要妥协。

另外,本文中提到的 Codemods 和进度报告脚本等工具也都是辅助迁移的必要前期开发。考虑到迁移往往是一次性的工作,因此对辅助工具开发上的投入,我们认为二八原则依然适用,应尽可能花少量时间满足多数需求,无需做到面面俱到。

如果有机会重新主导一次 RTL 测试迁移项目,以下两点依然有改进空间:

1. Codemods 的范围需要提前界定

虽然我们一直在强调二八原则和避免在一次性的 Codemods 工具上做过度投入,但工程师追求完善自动化工具的天性让我们依然在 Codemods 上花费了比预期更多的时间,实现了对相对不常见的代码模式及其变形的支持。预先引入团队计划和评估决策流程,将有效地避免这一问题。

2. 理应更早引入团队参与,加速项目进展

在项目后期,我们等 Codemods 相对稳定后才请更多团队成员参与进来。回头看,这种瀑布式的开发流程,使整个项目周期拉长了。更好的做法是我们预先确定测试迁移的典型代码模式,并对团队做预先培训。即便 Codemods 没有完全完成,我们依然可以动员整个前端团队,尽早开始手动迁移那些 Codemods 计划中不会支持的代码模式,做到项目的并行推进。

总体而言,此次测试迁移工作为 Tubi 前端团队扫清了之后升级最新 React 18 的障碍。在短短两个月,我们完成了 466 个测试文件和 10 余万行测试代码的迁移,这无疑是一个值得称道的成绩。同时,受益于 React Testing Library,团队实现 UI 测试代码的效率和对 Web Accessbility 的重视得到了极大提升,这也印证了我们对技术项目投入的价值与收益。

作者:Nick QI,Tubi Staff Software Engineer

欢迎加入 Tubi

如果你对类似项目感兴趣,欢迎加入 Tubi! 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/858616.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一个php文件搞定微信小程序订阅消息推送(含access_token的获取、缓存、刷新)

摘要 微信小程序的订阅消息功能具有多个优点&#xff0c;可以为开发者和用户带来便利和更好的体验。以下是一些主要的优点&#xff1a; **个性化消息推送&#xff1a; **订阅消息允许开发者向用户发送个性化的消息内容&#xff0c;根据用户的偏好和行为进行定制化推送&#x…

设计模式(5)代理模式

一、介绍&#xff1a; 【Subject/抽象角色】定义了RealSubject和Proxy的共用接口&#xff0c;这样就可以在任何使用RealSubject的地方都可以使用Proxy 【RealSubject/真实角色】定义Proxy所代表的真实实体 【Proxy/代理角色】保存一个引用使得代理可以访问实体&#xff0c;并…

GIT结合Maven对源码以及jar包的管理建设

一、GIT管理规范 1.1 git分支概念 develop分支 开发分支,不管是要做新的feature还是需要做bug修复,都是从这个分支分出来做。 在这个分支下主要负责记录开发状态下相对稳定的版本,即完成了某个feature或者修复了某个bug后的开发稳定版本。 feature-*-*分支 feature-姓名…

CLION编译后的exe文件添加ico图标

编译前准备 1.编译ico.rc>ico.o 将图标放到工程目录下 新建ico.rc文件 id ICON "spoon.ico" //添加图标 #include <winver.h> VS_VERSION_INFO VERSIONINFOFILEVERSION 1,0,0,10PRODUCTVERSION 1,0,0,10FILEFLAGSMASK 0x3fL #ifdef _DEBUGFILEFLAGS VS_FF…

Vue-2.nodejs的介绍和安装

nodejs简介 ► 创建 Node.js 应用:package.json 首先&#xff0c;创建一个新文件夹以便于容纳需要的所有文件&#xff0c;并且在此其中创建一个 package.json 文件&#xff0c;描述你应用程序以及需要的依赖&#xff1a; 配合着你的 package.json 请运行 npm install。如果你…

企业权限管理(七)-权限操作

1. 数据库与表结构 1.1 用户表 1.1.1 用户表信息描述 users 1.1.2 sql语句 CREATE TABLE users( id varchar2(32) default SYS_GUID() PRIMARY KEY, email VARCHAR2(50) UNIQUE NOT NULL, username VARCHAR2(50), PASSWORD VARCHAR2(50), phoneNum VARCHAR2(20), STATUS INT )…

内网穿透是什么意思?快解析如何实现内网穿透

在家里或者公司&#xff0c;我们常常会使用路由器来连接网络&#xff0c;以便我们能够上网学习和工作&#xff0c;但有时候使用起来真的不方便。有的时候我们在外面&#xff0c;想访问家里或者公司内部的设备&#xff0c;就会碰到一个问题&#xff1a;我们无法直接通过公网IP访…

yolov5代码解读之yolo.py【网络结构】

​这个文件阿对于做模型修改、模型创新有很好大好处。 首先加载一些python库和模块&#xff1a; 如果要执行这段代码&#xff0c;直接在终端输入python yolo.py. yolov5的模型定义和网络搭建都用到了model这个类(也就是以下图片展示的东西)&#xff1a;&#xff08;以前代码没…

接口数据处理

调取接口 增删改查 增 对接口的数据添加字段 url: urlData.filter((urlItem) > urlItem.id item.id),url: urlData.find((urlItem) > urlItem.id item.id)//理想&#xff0c;不能实现 url: urlData.find((urlItem) > {if (urlItem.id item.id) {return urlItem.u…

Linux Linux系统文件类型与文件权限

一、文件类型 &#xff08;1&#xff09;在windows系统中文件类型以文件的后缀名来区分&#xff0c;在Linux系统中文件类型不以后缀名来区分。注意编写c代码时必须写后缀名.c&#xff0c;不然C编译器不会编译该文件。 &#xff08;2&#xff09;在Linux系统中以文件的标志来区…

ROS入门-常见的rostopic命令及其用法的示例

目录 常见的rostopic命令及其用法的示例 1. 列出所有可用的话题&#xff1a; 2. 获取话题详细信息&#xff1a; 3. 实时显示话题消息内容&#xff1a; 4. 发布消息到话题&#xff1a; 5. 发布随机消息到话题&#xff1a; 6. 查看话题消息类型&#xff1a; 7. 查看话题消…

HTTP代理授权方式介绍

在网络爬虫过程中&#xff0c;我们经常需要使用HTTP代理来实现IP隐藏、突破限制或提高抓取效率。而为了确保代理的正常使用&#xff0c;并避免被滥用&#xff0c;代理服务商通常会采用授权方式。在本文中&#xff0c;我们将介绍几种常见的HTTP代理授权方式&#xff0c;以帮助你…

required a single bean, but 2 were found

1.问题描述 要求一个bean,但是发现了两个 说明&#xff1a; 当我们声明一个bean注解时&#xff0c;没有指定BeanName&#xff0c;Spring使用了默认值 spring的IOC容器中 默认一个类型只能有一个bean对象如果有两个bean对象&#xff0c;需要指定BeanName Bean //这个bean的名字…

Docker前置背景:架构演进

"但人类都环绕星球&#xff0c;我更愿追随彗星漂流~" 在正式引入架构演进之前&#xff0c;本小节会对一些比较重要、常见的概念进行介绍。 基本概念: (1)应用(application)/系统(system) 为了完成一整套服务的一个程序或者一组相互配合的程序群。生活例子类比&…

使用公网访问内网IIS网站服务器【无需公网IP】

使用公网访问内网IIS网站服务器【无需公网IP】 文章目录 使用公网访问内网IIS网站服务器【无需公网IP】前言1. 注册并安装cpolar2. 创建隧道映射3. 获取公网地址 前言 这里介绍通过内网穿透&#xff0c;实现公网访问内网IIS网站服务器。 都知道&#xff0c;现在基本不会被分配…

Qt自定义对话框

介绍 自定义框主要通过对现有对话框QDialog类的派生&#xff0c;根据需求编写成员函数、重载信号函数、槽函数&#xff0c;进而实现在主QWidget中点击某个按钮后&#xff0c;一个对话框的弹出 流程 简化创建派生类 最后点击完成即可。 自定义ui界面&#xff0c;编写成员函数…

pytest fixture 常用参数

fixture 常用的参数 参数一&#xff1a;autouse&#xff0c;作用&#xff1a;自动运行&#xff0c;无需调用 举例一&#xff1a;我们在类中定义一个function 范围的fixture; 设置它自动执行autouseTrue&#xff0c;那么我们看下它执行结果 输出&#xff1a; 说明&#xff1a;…

前端项目环境变量如何配置?

我们在项目开发过程中&#xff0c;至少会经历开发环境、测试环境和生产环境三个阶段。不同阶段请求的状态&#xff08;如接口地址等&#xff09;不尽相同&#xff0c;若手动切换接口地址是相当繁琐切容易出错的。于是环境变量配置的需求就应运而生&#xff0c;我们只需做简单的…

【论文简介】PP-OCR中文字符识别论文概述

相关论文 2009.PP-OCR: A Practical Ultra Lightweight OCR System 2109.PP-OCRv2: Bag of Tricks for Ultra Lightweight OCR System 2206.PP-OCRv3: More Attempts for the Improvement of Ultra Lightweight OCR System 工程代码&#xff1a; github_PaddleOCR | 国内gitee_…

FlexRay汽车总线静电防护,如何设计保护方案图?

FlexRay是一种高速、实时、可靠、具备故障容错能力的总线技术&#xff0c;是继CAN和LIN总线之后的最新研发成果。FlexRay为线控应用&#xff08;即线控驱动、线控转向、线控制动等&#xff09;提供了容错和时间确定性性能要求。虽然FlexRay将解决当前高端和未来主流车载网络的挑…