前端面试第 75 期 - 前端质量问题专题(11 道题)

2025.02.10 - 2025.03.25 更新前端面试问题总结(11道题)

获取更多面试相关问题可以访问

github 地址: github.com/pro-collect...

gitee 地址: gitee.com/yanleweb/in...

目录

中级开发者相关问题【共计 3 道题】

  1. 如何排除样式文件、图片等资源文件进行单测【热度: 173】【工程化】
  2. jest 有哪些重要配置?【热度: 180】【工程化】
  3. 该如何给自己的项目添加 jest 去测试 react ts 项目【热度: 122】【工程化】

高级开发者相关问题【共计 8 道题】

  1. 如何搭建前端测试环境【热度: 505】【工程化】

  2. 前端 单元测试, react 项目为例, 该如何做单测选型【热度: 405】【工程化】

  3. 前端 e2e 测试, 该如何选型【热度: 335】【工程化】

  4. 如何保障前端项目质量【热度: 717】【工程化】

  5. 前端单测,如何通过单测模拟请求【热度: 265】【工程化】

  6. jest 单测, 如何测试 react 组件交互【热度: 200】【工程化】

  7. 如何 对 react 状态库进行单测, 比如 redux、recoil 等状态库【热度: 170】【工程化】

  8. 单测中,如果有一些三方依赖,想排除这个三方依赖进行测试,该如何做?【热度: 387】【工程化】

中级开发者相关问题【共计 3 道题】

1107. 如何排除样式文件、图片等资源文件进行单测【热度: 173】【工程化】

关键词:前端单测,如何排除样式文件、图片

在进行前端单元测试时,样式文件(如 CSS、SCSS 等)和图片等资源文件通常不会影响代码的逻辑功能,为了让测试更加专注于代码逻辑,避免不必要的依赖和错误,需要排除这些资源文件。下面分别介绍使用不同测试框架(如 Jest)时的处理方法。

使用 Jest 排除资源文件

1. 样式文件处理

对于样式文件,可使用 identity-obj-proxy 包来模拟样式导入。identity-obj-proxy 会将所有的 CSS 类名转换为相同的字符串,从而避免实际加载 CSS 文件。

安装依赖

bash 复制代码
npm install --save-dev identity-obj-proxy

配置 Jestjest.config.jspackage.json 中的 jest 配置里添加如下内容:

javascript 复制代码
module.exports = {
  // ...其他配置
  moduleNameMapper: {
    "\\.(css|less|scss|sass)$": "identity-obj-proxy",
  },
};

或者在 package.json 中:

json 复制代码
{
  "jest": {
    "moduleNameMapper": {
      "\\.(css|less|scss|sass)$": "identity-obj-proxy"
    }
  }
}

这样,当在测试代码中导入 CSS 文件时,Jest 会使用 identity-obj-proxy 来模拟,而不会真正加载 CSS 文件。

示例代码 假设有如下组件:

jsx 复制代码
// MyComponent.jsx
import React from "react";
import "./styles.css";

const MyComponent = () => {
  return <div className="my-class">Hello, World!</div>;
};

export default MyComponent;

测试代码可以正常编写,不用担心 styles.css 文件的加载问题:

javascript 复制代码
// MyComponent.test.js
import React from "react";
import { render, screen } from "@testing-library/react";
import MyComponent from "./MyComponent";

test("renders MyComponent", () => {
  render(<MyComponent />);
  const element = screen.getByText("Hello, World!");
  expect(element).toBeInTheDocument();
});
2. 图片文件处理

对于图片文件,可使用一个简单的模块来模拟图片导入。

配置 Jestjest.config.jspackage.json 中的 jest 配置里添加如下内容:

javascript 复制代码
module.exports = {
  // ...其他配置
  moduleNameMapper: {
    "\\.(jpg|jpeg|png|gif|svg)$": "<rootDir>/__mocks__/fileMock.js",
  },
};

或者在 package.json 中:

json 复制代码
{
  "jest": {
    "moduleNameMapper": {
      "\\.(jpg|jpeg|png|gif|svg)$": "<rootDir>/__mocks__/fileMock.js"
    }
  }
}

创建模拟文件 在项目根目录下创建 __mocks__ 文件夹,并在其中创建 fileMock.js 文件,内容如下:

javascript 复制代码
module.exports = "test-file-stub";

这样,当在测试代码中导入图片文件时,Jest 会使用 fileMock.js 中的模拟值来替代,而不会真正加载图片文件。

示例代码 假设有如下组件:

jsx 复制代码
// ImageComponent.jsx
import React from "react";
import myImage from "./my-image.png";

const ImageComponent = () => {
  return <img src={myImage} alt="Test Image" />;
};

export default ImageComponent;

测试代码可以正常编写,不用担心 my-image.png 文件的加载问题:

javascript 复制代码
// ImageComponent.test.js
import React from "react";
import { render, screen } from "@testing-library/react";
import ImageComponent from "./ImageComponent";

test("renders ImageComponent", () => {
  render(<ImageComponent />);
  const imageElement = screen.getByAltText("Test Image");
  expect(imageElement).toBeInTheDocument();
});

通过以上配置,你可以在 Jest 单元测试中排除样式文件和图片等资源文件的干扰,专注于代码逻辑的测试。

1108. jest 有哪些重要配置?【热度: 180】【工程化】

关键词:前端单测 jest 配置

Jest 有许多重要配置项,它们能帮助你根据项目需求定制测试环境和流程。以下是一些关键的 Jest 配置项及其用途:

1. testMatch

  • 作用:指定 Jest 要测试的文件匹配模式。Jest 会在项目中查找符合这些模式的文件并执行测试。
  • 示例
javascript 复制代码
module.exports = {
  testMatch: ["**/__tests__/**/*.js?(x)", "**/?(*.)+(spec|test).js?(x)"],
};

上述配置表示 Jest 会查找 __tests__ 目录下的所有 .js.jsx 文件,以及文件名包含 spectest.js.jsx 文件。

2. moduleNameMapper

  • 作用:用于映射模块名,可将特定的模块名映射到另一个模块或文件。常见用途是处理样式文件、图片等资源文件的导入,避免在测试时实际加载这些资源。
  • 示例
javascript 复制代码
module.exports = {
  moduleNameMapper: {
    "\\.(css|less|scss|sass)$": "identity-obj-proxy",
    "\\.(jpg|jpeg|png|gif|svg)$": "<rootDir>/__mocks__/fileMock.js",
  },
};

这里将 CSS 等样式文件映射到 identity-obj-proxy,将图片文件映射到自定义的模拟文件 fileMock.js

3. setupFilesAfterEnv

  • 作用:在每个测试文件运行前执行的脚本文件列表。常用于设置测试环境,如引入测试工具、全局配置等。
  • 示例
javascript 复制代码
module.exports = {
  setupFilesAfterEnv: ["<rootDir>/src/setupTests.js"],
};

setupTests.js 文件中可以进行一些全局的测试配置,例如引入 @testing-library/jest-dom 扩展断言:

javascript 复制代码
import "@testing-library/jest-dom";

4. testEnvironment

  • 作用 :指定测试运行的环境,常见的值有 nodejsdomnode 适用于 Node.js 环境的测试,jsdom 模拟了浏览器环境,适用于前端 JavaScript 代码的测试。
  • 示例
javascript 复制代码
module.exports = {
  testEnvironment: "jsdom",
};

5. coverageDirectory

  • 作用:指定代码覆盖率报告的输出目录。
  • 示例
javascript 复制代码
module.exports = {
  coverageDirectory: "<rootDir>/coverage",
};

6. coverageThreshold

  • 作用:设置代码覆盖率的阈值。可以为全局或特定文件设置分支、函数、行和语句的覆盖率阈值,如果测试结果未达到这些阈值,Jest 会报错。
  • 示例
javascript 复制代码
module.exports = {
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
  },
};

7. transform

  • 作用:指定如何转换不同类型的文件。通常用于处理 Babel 转换,将 ES6+ 代码转换为兼容的 JavaScript 代码。
  • 示例
javascript 复制代码
module.exports = {
  transform: {
    "^.+\\.(js|jsx)$": "babel-jest",
  },
};

上述配置表示使用 babel-jest 来转换 .js.jsx 文件。

8. verbose

  • 作用 :一个布尔值,控制是否在测试结果中显示每个测试用例的详细信息。设置为 true 会显示更详细的测试结果。
  • 示例
javascript 复制代码
module.exports = {
  verbose: true,
};

9. watchPlugins

  • 作用 :指定在 watch 模式下使用的插件。例如,jest-watch-typeahead 插件可以让你在 watch 模式下通过输入文件名或测试用例名来快速过滤测试。
  • 示例
javascript 复制代码
module.exports = {
  watchPlugins: ["jest-watch-typeahead/filename", "jest-watch-typeahead/testname"],
};

这些配置项能帮助你更好地定制 Jest 的行为,满足不同项目的测试需求。你可以根据项目的具体情况进行选择和配置。

1109. 该如何给自己的项目添加 jest 去测试 react ts 项目【热度: 122】【工程化】

关键词:前端测试 jest 配置

当使用 Babel 编译 React TypeScript 项目并配置 Jest 时,可按以下步骤进行:

1. 安装必要的依赖

在项目根目录下,通过以下命令安装所需的依赖:

bash 复制代码
npm install --save-dev jest babel-jest @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript @types/jest @testing-library/react @testing-library/jest-dom @testing-library/user-event
  • jest:测试框架。
  • babel-jest:让 Jest 能使用 Babel 转换代码。
  • @babel/core:Babel 的核心库。
  • @babel/preset-env:根据目标环境转换代码。
  • @babel/preset-react:处理 React 的 JSX 语法。
  • @babel/preset-typescript:支持 TypeScript 代码的转换。
  • @types/jest:为 Jest 提供 TypeScript 类型定义。
  • @testing-library/react:用于测试 React 组件的工具库。
  • @testing-library/jest-dom:提供额外的 DOM 断言方法。
  • @testing-library/user-event:模拟用户交互的工具库。

2. 配置 Babel

在项目根目录下创建或修改 .babelrc 文件(也可以使用 babel.config.js),添加以下配置:

json 复制代码
{
  "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"]
}
  • @babel/preset-env:依据 package.json 里的 browserslist 配置转换代码。
  • @babel/preset-react:处理 React 的 JSX 语法。
  • @babel/preset-typescript:将 TypeScript 代码转换为 JavaScript 代码。

3. 配置 Jest

在项目根目录下创建 jest.config.js 文件,并添加以下配置:

javascript 复制代码
module.exports = {
  // 使用 babel-jest 转换文件
  transform: {
    "^.+\\.(js|jsx|ts|tsx)$": "babel-jest",
  },
  // 配置测试环境
  testEnvironment: "jsdom",
  // 配置模块名映射,处理样式和图片文件
  moduleNameMapper: {
    "\\.(css|less|scss|sass)$": "identity-obj-proxy",
    "\\.(jpg|jpeg|png|gif|svg)$": "<rootDir>/__mocks__/fileMock.js",
  },
  // 在每个测试文件运行前执行的脚本
  setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"],
  // 识别的文件扩展名
  moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
};
  • transform:指定使用 babel-jest 转换 .js.jsx.ts.tsx 文件。
  • testEnvironment:设置测试环境为 jsdom,模拟浏览器环境。
  • moduleNameMapper:处理样式文件和图片文件的导入,避免在测试时实际加载这些资源。
  • setupFilesAfterEnv:指定在每个测试文件运行前执行的脚本文件。
  • moduleFileExtensions:指定 Jest 识别的文件扩展名。

4. 创建模拟文件

在项目根目录下创建 __mocks__ 文件夹,并在其中创建 fileMock.js 文件,内容如下:

javascript 复制代码
module.exports = "test-file-stub";

该文件用于模拟图片等资源文件的导入。

5. 创建测试设置文件

src 目录下创建 setupTests.ts 文件,并添加以下内容:

typescript 复制代码
import "@testing-library/jest-dom";

这会引入 @testing-library/jest-dom 提供的额外 DOM 断言方法。

6. 配置 package.json

package.json 文件中添加测试脚本:

json 复制代码
{
  "scripts": {
    "test": "jest"
  }
}

7. 编写并运行测试用例

例如,有一个简单的 React TypeScript 组件 App.tsx

tsx 复制代码
import React from "react";

const App: React.FC = () => {
  return <h1>Hello, Jest!</h1>;
};

export default App;

对应的测试文件 App.test.tsx 可以这样写:

typescript 复制代码
import React from "react";
import { render, screen } from "@testing-library/react";
import App from "./App";

test("renders App component", () => {
  render(<App />);
  const element = screen.getByText("Hello, Jest!");
  expect(element).toBeInTheDocument();
});

在终端运行以下命令来执行测试:

bash 复制代码
npm test

通过以上步骤,你就可以在使用 Babel 编译的 React TypeScript 项目中配置好 Jest 进行测试了。

高级开发者相关问题【共计 8 道题】

1099. 如何搭建前端测试环境【热度: 505】【工程化】

关键词:前端测试

搭建前端测试体系是一个系统性工程,下面从几个关键方面详细介绍其搭建步骤:

1. 规划测试策略

  • 确定测试范围:明确需要测试的前端项目部分,例如页面布局、交互功能、组件、API 调用等。要考虑项目的规模、复杂度以及业务的关键功能点。
  • 设定测试目标:比如保证代码质量、提升用户体验、减少线上故障等。不同的目标会影响测试的深度和广度。
  • 制定测试计划:规划测试的阶段、时间节点、参与人员等。例如,在开发的不同阶段安排不同类型的测试,如单元测试在开发过程中同步进行,集成测试在模块合并后开展等。

2. 选择测试框架和工具

单元测试
  • Jest:功能强大且易于上手,有丰富的断言库和模拟功能,自带测试运行器,能自动并行执行测试用例,广泛应用于 React、Vue 等项目。
  • Mocha:灵活度高,可搭配不同的断言库(如 Chai)和模拟库(如 Sinon)使用,支持异步测试,适用于各种 JavaScript 项目。
集成测试
  • React Testing Library:专注于从用户交互角度测试 React 组件,鼓励编写接近用户使用场景的测试用例。
  • Vue Test Utils:是 Vue.js 官方提供的测试工具,能方便地挂载和测试 Vue 组件,处理组件间的交互。
端到端(E2E)测试
  • Cypress:具有实时重新加载、自动等待等特性,能直观展示测试运行过程,易于调试,适合各种前端项目,尤其是单页面应用。
  • Puppeteer:基于 Chrome 或 Chromium 浏览器,可模拟复杂的用户操作,如页面滚动、文件上传等,常用于性能测试和自动化操作。

3. 编写测试用例

单元测试用例

针对单个函数、组件或模块编写测试用例,确保其功能的正确性。例如,对于一个计算两个数之和的函数,可以编写以下测试用例:

javascript 复制代码
function sum(a, b) {
  return a + b;
}

test("adds 1 + 2 to equal 3", () => {
  expect(sum(1, 2)).toBe(3);
});
集成测试用例

测试多个组件或模块之间的交互是否正常。例如,测试一个表单组件与数据提交逻辑的集成:

javascript 复制代码
import React from "react";
import { render, fireEvent } from "@testing-library/react";
import Form from "./Form";

test("form submits data correctly", () => {
  const mockSubmit = jest.fn();
  const { getByLabelText, getByText } = render(<Form onSubmit={mockSubmit} />);
  const input = getByLabelText("Name");
  const submitButton = getByText("Submit");

  fireEvent.change(input, { target: { value: "John" } });
  fireEvent.click(submitButton);

  expect(mockSubmit).toHaveBeenCalledWith({ name: "John" });
});
端到端测试用例

模拟用户在浏览器中的真实操作流程,确保整个应用的功能正常。例如,使用 Cypress 测试一个登录页面:

javascript 复制代码
describe("Login page", () => {
  it("logs in successfully", () => {
    cy.visit("/login");
    cy.get('input[name="username"]').type("testuser");
    cy.get('input[name="password"]').type("testpassword");
    cy.get('button[type="submit"]').click();
    cy.url().should("include", "/dashboard");
  });
});

4. 集成持续集成/持续部署(CI/CD)

将测试集成到 CI/CD 流程中,每次代码提交或合并时自动触发测试。常见的 CI/CD 工具有 Jenkins、GitLab CI/CD 和 GitHub Actions。以下是一个使用 GitHub Actions 运行 Jest 单元测试的示例配置文件:

yaml 复制代码
name: Test

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: 14
      - name: Install dependencies
        run: npm install
      - name: Run tests
        run: npm test

5. 代码覆盖率检测

使用工具如 Istanbul(Jest 内置支持)检测代码的测试覆盖率,确保测试覆盖到尽可能多的代码。可以在 CI/CD 流程中设置代码覆盖率阈值,若不达标则阻止代码部署。例如,在 Jest 配置中设置覆盖率阈值:

json 复制代码
{
  "coverageThreshold": {
    "global": {
      "branches": 80,
      "functions": 80,
      "lines": 80,
      "statements": 80
    }
  }
}

6. 性能测试和监控

使用工具如 Lighthouse、WebPageTest 对前端应用的性能进行测试,包括页面加载时间、响应时间等指标。根据测试结果对代码和资源进行优化。同时,建立性能监控系统,持续跟踪应用的性能变化。

7. 维护和优化测试体系

随着项目的发展,不断维护和更新测试用例,确保测试的有效性和准确性。定期审查测试体系,根据项目需求和技术发展进行调整和优化。例如,引入新的测试框架或工具,改进测试用例的编写方式等。

1100. 前端 单元测试, react 项目为例, 该如何做单测选型【热度: 405】【工程化】

关键词:前端测试

在 React 项目中进行单元测试选型时,你需要综合考量测试框架、断言库、模拟工具等多个方面,以下是详细的选型建议:

测试框架

Jest
  • 优点
    • 功能集成度高:集测试运行器、断言库、模拟功能于一体,无需额外安装大量依赖,配置简单,能快速上手。
    • 性能出色:支持并行测试,可显著缩短测试时间。同时,它具备快照测试功能,能方便地对组件的渲染结果进行比对。
    • 社区生态丰富:有大量的插件和工具可供使用,社区文档完善,遇到问题容易找到解决方案。
  • 适用场景:适合大多数 React 项目,尤其是初学者和追求高效测试流程的团队。
  • 示例代码
javascript 复制代码
import React from "react";
import { render } from "@testing-library/react";
import MyComponent from "./MyComponent";

test("renders MyComponent correctly", () => {
  const { getByText } = render(<MyComponent />);
  const element = getByText(/Hello, World!/i);
  expect(element).toBeInTheDocument();
});
Mocha
  • 优点
    • 灵活性强:可以与各种断言库(如 Chai)和模拟库(如 Sinon)自由搭配,满足不同项目的个性化需求。
    • 跨环境支持:既能在浏览器环境中运行,也能在 Node.js 环境中运行,方便进行不同环境下的测试。
  • 适用场景:适用于对测试流程有特殊要求,或者需要与其他工具深度集成的 React 项目。
  • 示例代码
javascript 复制代码
const assert = require("assert");
const React = require("react");
const { render } = require("@testing-library/react");
const MyComponent = require("./MyComponent");

describe("MyComponent", function () {
  it("should render with correct text", function () {
    const { getByText } = render(<MyComponent />);
    const element = getByText(/Hello, World!/i);
    assert.ok(element);
  });
});

断言库

Jest 内置断言
  • 优点:与 Jest 框架无缝集成,语法简洁易懂,能满足大部分常见的断言需求。
  • 适用场景:在使用 Jest 作为测试框架时,优先使用其内置断言。
  • 示例代码
javascript 复制代码
const result = 1 + 2;
expect(result).toBe(3);
Chai
  • 优点:提供了多种断言风格(如 BDD、TDD),可根据团队的编程习惯选择合适的风格。它还支持与不同的测试框架集成。
  • 适用场景:当需要更丰富的断言方式,或者在使用 Mocha 等框架时,Chai 是一个不错的选择。
  • 示例代码
javascript 复制代码
const chai = require("chai");
const expect = chai.expect;
const result = 1 + 2;
expect(result).to.equal(3);

模拟工具

Jest Mock
  • 优点:Jest 内置的模拟功能强大,能轻松模拟函数、模块和组件。它可以自动跟踪函数的调用情况,方便进行断言。
  • 适用场景:在使用 Jest 进行测试时,使用其内置的模拟功能即可满足大部分需求。
  • 示例代码
javascript 复制代码
const mockFunction = jest.fn();
mockFunction();
expect(mockFunction).toHaveBeenCalled();
Sinon
  • 优点:功能全面,支持创建间谍(spy)、存根(stub)和模拟(mock)对象。它可以独立于测试框架使用,与各种断言库兼容。
  • 适用场景:当需要更复杂的模拟功能,或者在使用 Mocha 等框架时,Sinon 是一个很好的选择。
  • 示例代码
javascript 复制代码
const sinon = require("sinon");
const myObject = {
  myMethod: function () {},
};
const spy = sinon.spy(myObject, "myMethod");
myObject.myMethod();
expect(spy.called).to.be.true;

测试工具库

React Testing Library
  • 优点:专注于从用户交互的角度测试组件,鼓励编写接近用户实际使用场景的测试用例。它不依赖于组件的内部实现细节,能提高测试的稳定性。
  • 适用场景:适用于各种 React 项目,尤其是注重用户体验和组件交互的项目。
  • 示例代码
javascript 复制代码
import React from "react";
import { render, fireEvent } from "@testing-library/react";
import MyButton from "./MyButton";

test("button click event", () => {
  const mockClick = jest.fn();
  const { getByText } = render(<MyButton onClick={mockClick} />);
  const button = getByText("Click me");
  fireEvent.click(button);
  expect(mockClick).toHaveBeenCalled();
});
Enzyme
  • 优点:提供了丰富的 API 来操作和断言 React 组件的输出。它支持浅渲染(shallow rendering),可以只渲染组件本身,而不渲染其子组件,方便进行单元测试。
  • 适用场景:适用于需要深入测试组件内部结构和状态的项目。不过,随着 React 官方推荐从实现细节转向用户交互的测试方式,Enzyme 的使用逐渐减少。
  • 示例代码
javascript 复制代码
import React from "react";
import { shallow } from "enzyme";
import MyComponent from "./MyComponent";

test("MyComponent should render correctly", () => {
  const wrapper = shallow(<MyComponent />);
  expect(wrapper.find("div").length).toBe(1);
});

在选型时,你需要根据项目的规模、复杂度、团队技术栈和个人偏好等因素进行综合考虑,选择最适合项目的测试方案。

1101. 前端 e2e 测试, 该如何选型【热度: 335】【工程化】

关键词:前端测试

在进行前端端到端(E2E)测试选型时,需要综合考虑多方面因素,如测试框架的功能特性、易用性、社区支持、性能等。以下为你介绍几种常见的前端 E2E 测试工具,并给出选型建议。

常见前端 E2E 测试工具

1. Cypress
  • 优点
    • 易于上手:提供了简洁直观的 API,测试代码编写简单,无需复杂的配置即可快速开始测试。
    • 实时反馈:具有实时重新加载功能,在修改测试代码时能即时看到测试结果,提高开发和调试效率。
    • 自动等待:Cypress 会自动等待元素加载完成、动画结束等,无需手动添加等待时间,减少测试代码的复杂性。
    • 可视化调试:提供可视化界面,可直观查看测试执行过程,方便定位问题。
  • 缺点
    • 仅支持 Chrome 家族浏览器:在跨浏览器测试方面存在一定局限性。
    • 项目规模限制:对于大型项目,测试运行速度可能会受到影响。
  • 适用场景:适合小型到中型的前端项目,尤其是单页面应用(SPA),注重开发效率和调试体验的团队。
2. Puppeteer
  • 优点
    • 强大的自动化能力:基于 Chrome 或 Chromium 浏览器,可模拟各种复杂的用户操作,如页面滚动、文件上传、截图等。
    • 性能出色:直接控制浏览器,无需额外的中间层,测试执行速度快。
    • 灵活度高:可与其他测试框架和工具集成,满足不同的测试需求。
  • 缺点
    • 学习曲线较陡:API 相对复杂,对于初学者来说上手难度较大。
    • 缺乏内置断言:需要手动集成断言库来进行结果验证。
  • 适用场景:适合需要模拟复杂用户操作、进行性能测试或与其他工具深度集成的项目。
3. Selenium WebDriver
  • 优点
    • 跨浏览器支持:支持多种主流浏览器,如 Chrome、Firefox、Safari 等,可进行全面的跨浏览器测试。
    • 多语言支持:支持多种编程语言,如 Java、Python、JavaScript 等,方便不同技术栈的团队使用。
    • 社区成熟:拥有庞大的社区和丰富的文档资源,遇到问题容易找到解决方案。
  • 缺点
    • 配置复杂:需要手动配置浏览器驱动和环境,测试代码编写和维护成本较高。
    • 性能问题:由于需要通过 WebDriver 协议与浏览器进行交互,测试执行速度相对较慢。
  • 适用场景:适合大型项目和对跨浏览器兼容性要求较高的项目,尤其是企业级应用。
4. Playwright
  • 优点
    • 跨浏览器和跨平台:支持 Chrome、Firefox、Safari 等多种浏览器,且能在不同操作系统上运行,提供统一的 API。
    • 自动等待和智能断言:具备自动等待机制,同时提供智能断言功能,简化测试代码编写。
    • 录制和回放:支持录制用户操作并生成测试代码,方便快速创建测试用例。
  • 缺点
    • 社区相对较小:与 Selenium 相比,社区资源和生态系统还不够完善。
  • 适用场景:适合需要进行跨浏览器测试,且希望提高测试开发效率的项目。

选型建议

  • 项目规模和复杂度

    • 小型项目:Cypress 是一个不错的选择,其简单易用的特点可以帮助团队快速搭建测试环境。
    • 中型项目:Puppeteer 或 Playwright 可以满足对自动化能力和跨浏览器支持的需求,同时保持较高的测试效率。
    • 大型项目:Selenium WebDriver 凭借其强大的跨浏览器支持和成熟的社区生态,更适合应对复杂的测试场景。
  • 团队技术栈

    • 如果团队主要使用 JavaScript 进行开发,Cypress、Puppeteer 和 Playwright 会更容易集成到现有项目中。
    • 如果团队熟悉 Java 或 Python 等编程语言,Selenium WebDriver 是一个合适的选择。
  • 测试需求

    • 如果需要进行大量的性能测试和复杂的用户操作模拟,Puppeteer 更具优势。
    • 如果对跨浏览器兼容性要求极高,Selenium WebDriver 或 Playwright 是首选。
    • 如果注重开发效率和调试体验,Cypress 会是更好的选择。

综上所述,在选择前端 E2E 测试工具时,需要根据项目的实际情况和团队的技术能力进行综合考虑,以确保选择的工具能够满足项目的测试需求。

1102. 如何保障前端项目质量【热度: 717】【工程化】

关键词:前端项目质量保证

保障前端项目质量可从开发流程、代码规范、测试体系、部署监控等多方面入手,以下是详细介绍:

遵循规范的开发流程

  • 需求分析与设计:和产品经理、设计师等充分沟通,理解项目需求和设计方案。输出详细的需求文档和设计稿,为后续开发提供清晰的指引。
  • 代码编写:按照设计方案进行代码开发,采用模块化、组件化的开发方式,提高代码的可维护性和可复用性。
  • 代码审查:组织团队成员进行代码审查,检查代码是否符合规范、是否存在潜在的问题。可以使用工具如 ESLint、Stylelint 等进行静态代码分析。
  • 测试:开展单元测试、集成测试和端到端测试,确保代码的功能正确性。同时,进行性能测试、兼容性测试等,保证项目在不同环境下的稳定性和性能表现。
  • 部署上线:借助自动化部署工具,如 Jenkins、GitLab CI/CD 等,将代码部署到生产环境。部署前进行预发布环境测试,确保上线的稳定性。

制定并遵守代码规范

  • 语法规范:依据项目使用的前端技术栈,制定相应的语法规范。例如,使用 ESLint 规范 JavaScript 代码,使用 Stylelint 规范 CSS 代码。
  • 命名规范:统一变量名、函数名、类名等的命名规则,提高代码的可读性。例如,采用驼峰命名法或下划线命名法。
  • 代码风格:统一代码的缩进、空格、注释等风格,使代码具有一致性。可以使用 Prettier 等工具自动格式化代码。

构建完善的测试体系

  • 单元测试:针对单个函数、组件或模块编写测试用例,确保其功能的正确性。常用的单元测试框架有 Jest、Mocha 等。
  • 集成测试:测试多个组件或模块之间的交互是否正常。可以使用 React Testing Library、Vue Test Utils 等工具进行集成测试。
  • 端到端测试:模拟用户在浏览器中的真实操作,确保整个应用的功能正常。常用的端到端测试框架有 Cypress、Puppeteer 等。
  • 性能测试:使用工具如 Lighthouse、WebPageTest 对前端应用的性能进行测试,包括页面加载时间、响应时间等指标。根据测试结果对代码和资源进行优化。
  • 兼容性测试:在不同的浏览器(如 Chrome、Firefox、Safari 等)和设备(如手机、平板、电脑等)上进行测试,确保项目的兼容性。

实施监控与反馈机制

  • 错误监控:使用工具如 Sentry 对前端应用的错误进行监控,及时发现和解决线上问题。

  • 性能监控:持续监控前端应用的性能指标,如页面加载时间、响应时间等,及时发现性能瓶颈并进行优化。

  • 用户反馈:收集用户的反馈意见,了解用户在使用过程中遇到的问题和需求,及时进行改进。

1103. 前端单测,如何通过单测模拟请求【热度: 265】【工程化】

关键词:前端单测模拟请求

在前端单元测试里,模拟请求是很重要的操作,它能让你在不依赖真实后端服务的情况下对前端代码进行测试。以下以 React 项目为例,结合不同的测试框架和模拟工具,为你介绍模拟请求的方法。

使用 Jest 和 axios-mock-adapter 模拟 axios 请求

axios 是常用的 HTTP 请求库,axios-mock-adapter 能方便地模拟 axios 请求。

安装依赖
bash 复制代码
npm install --save-dev axios-mock-adapter
示例代码

假设你有一个使用 axios 发送请求的组件:

jsx 复制代码
// UserList.jsx
import React, { useEffect, useState } from "react";
import axios from "axios";

const UserList = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        const response = await axios.get("https://api.example.com/users");
        setUsers(response.data);
      } catch (error) {
        console.error("Error fetching users:", error);
      }
    };
    fetchUsers();
  }, []);

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};

export default UserList;

对应的单元测试代码如下:

javascript 复制代码
// UserList.test.js
import React from "react";
import { render, waitFor } from "@testing-library/react";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import UserList from "./UserList";

describe("UserList", () => {
  let mock;

  beforeEach(() => {
    // 创建 axios 模拟适配器
    mock = new MockAdapter(axios);
  });

  afterEach(() => {
    // 重置模拟适配器
    mock.restore();
  });

  test("should render user list after successful fetch", async () => {
    const mockUsers = [
      { id: 1, name: "User 1" },
      { id: 2, name: "User 2" },
    ];
    // 模拟 GET 请求
    mock.onGet("https://api.example.com/users").reply(200, mockUsers);

    const { getByText } = render(<UserList />);

    // 等待请求完成
    await waitFor(() => {
      expect(getByText("User 1")).toBeInTheDocument();
      expect(getByText("User 2")).toBeInTheDocument();
    });
  });
});

在这个示例中,axios-mock-adapter 被用于模拟 axiosGET 请求。beforeEach 函数在每个测试用例执行前创建模拟适配器,afterEach 函数在测试用例执行后重置适配器。mock.onGet 方法用于指定要模拟的请求 URL,并返回模拟的响应数据。

使用 Jest 内置的 jest.fn() 模拟请求函数

如果你没有使用 axios 这类请求库,而是自定义了请求函数,可使用 Jest 的 jest.fn() 来模拟。

示例代码
javascript 复制代码
// api.js
export const fetchData = async () => {
  const response = await fetch("https://api.example.com/data");
  return response.json();
};
javascript 复制代码
// api.test.js
import { fetchData } from "./api";

jest.mock("./api", () => ({
  fetchData: jest.fn(),
}));

describe("fetchData", () => {
  test("should return mock data", async () => {
    const mockData = { message: "Mock data" };
    fetchData.mockResolvedValue(mockData);

    const result = await fetchData();
    expect(result).toEqual(mockData);
  });
});

这里使用 jest.mock 来模拟 fetchData 函数,mockResolvedValue 方法用于指定模拟函数的返回值。

通过上述方法,你可以在前端单元测试中有效地模拟请求,从而更全面地对前端代码进行测试。

1104. jest 单测, 如何测试 react 组件交互【热度: 200】【工程化】

关键词:前端单测模拟 react 交互

在 Jest 中测试 React 组件交互,通常会结合 React Testing Library 一起使用,它专注于从用户交互的角度来测试组件,鼓励编写接近用户实际使用场景的测试用例。以下将详细介绍测试 React 组件交互的步骤和示例。

1. 安装依赖

首先要确保项目中安装了 Jest、React Testing Library 以及相关的 React 依赖。若还未安装,可使用以下命令进行安装:

bash 复制代码
npm install --save-dev jest @testing-library/react @testing-library/jest-dom

2. 编写测试用例

下面通过几个不同交互场景的示例,展示如何使用 Jest 和 React Testing Library 测试 React 组件的交互。

示例 1:测试按钮点击事件

假设存在一个简单的 React 组件,点击按钮会更新文本内容:

jsx 复制代码
// ButtonComponent.jsx
import React, { useState } from "react";

const ButtonComponent = () => {
  const [text, setText] = useState("未点击");
  const handleClick = () => {
    setText("已点击");
  };
  return (
    <div>
      <p>{text}</p>
      <button onClick={handleClick}>点击我</button>
    </div>
  );
};

export default ButtonComponent;

对应的测试代码如下:

javascript 复制代码
// ButtonComponent.test.js
import React from "react";
import { render, fireEvent, screen } from "@testing-library/react";
import ButtonComponent from "./ButtonComponent";

test("点击按钮后文本更新", () => {
  render(<ButtonComponent />);
  // 查找按钮元素
  const button = screen.getByText("点击我");
  // 查找文本元素
  const textElement = screen.getByText("未点击");
  // 模拟点击事件
  fireEvent.click(button);
  // 断言文本内容是否更新
  expect(screen.getByText("已点击")).toBeInTheDocument();
});

在这个测试用例中,首先使用 render 函数渲染组件,接着使用 screen.getByText 方法查找按钮和文本元素,再用 fireEvent.click 模拟点击按钮的操作,最后通过 expect 断言文本内容是否更新。

示例 2:测试输入框输入事件

假设有一个输入框组件,输入内容后会显示输入的值:

jsx 复制代码
// InputComponent.jsx
import React, { useState } from "react";

const InputComponent = () => {
  const [inputValue, setInputValue] = useState("");
  const handleChange = (e) => {
    setInputValue(e.target.value);
  };
  return (
    <div>
      <input type="text" onChange={handleChange} />
      <p>{inputValue}</p>
    </div>
  );
};

export default InputComponent;

对应的测试代码如下:

javascript 复制代码
// InputComponent.test.js
import React from "react";
import { render, fireEvent, screen } from "@testing-library/react";
import InputComponent from "./InputComponent";

test("输入框输入内容后文本更新", () => {
  render(<InputComponent />);
  // 查找输入框元素
  const inputElement = screen.getByRole("textbox");
  // 模拟输入操作
  fireEvent.change(inputElement, { target: { value: "测试内容" } });
  // 断言文本内容是否更新
  expect(screen.getByText("测试内容")).toBeInTheDocument();
});

此测试用例中,先渲染组件,然后使用 screen.getByRole 方法查找输入框元素,通过 fireEvent.change 模拟输入操作,最后断言显示的文本内容是否正确。

示例 3:测试表单提交事件

假设有一个表单组件,提交表单后会显示提交成功的提示:

jsx 复制代码
// FormComponent.jsx
import React, { useState } from "react";

const FormComponent = () => {
  const [submitted, setSubmitted] = useState(false);
  const handleSubmit = (e) => {
    e.preventDefault();
    setSubmitted(true);
  };
  return (
    <form onSubmit={handleSubmit}>
      <input type="text" />
      <button type="submit">提交</button>
      {submitted && <p>提交成功</p>}
    </form>
  );
};

export default FormComponent;

对应的测试代码如下:

javascript 复制代码
// FormComponent.test.js
import React from "react";
import { render, fireEvent, screen } from "@testing-library/react";
import FormComponent from "./FormComponent";

test("表单提交后显示提交成功提示", () => {
  render(<FormComponent />);
  // 查找提交按钮元素
  const submitButton = screen.getByText("提交");
  // 模拟表单提交事件
  fireEvent.click(submitButton);
  // 断言是否显示提交成功提示
  expect(screen.getByText("提交成功")).toBeInTheDocument();
});

在这个测试用例里,同样先渲染组件,使用 screen.getByText 方法查找提交按钮元素,通过 fireEvent.click 模拟表单提交操作,最后断言是否显示提交成功的提示信息。

通过以上示例,你可以掌握在 Jest 中使用 React Testing Library 测试 React 组件交互的基本方法。在实际项目中,可根据组件的具体交互逻辑编写相应的测试用例。

1105. 如何 对 react 状态库进行单测, 比如 redux、recoil 等状态库【热度: 170】【工程化】

关键词:前端单测模拟 react 状态

下面分别介绍如何对 Redux 和 Recoil 这两种常见的 React 状态库进行单元测试。

测试 Redux

Redux 是一个可预测的状态容器,主要由 actions、reducers 和 store 构成。单元测试时可分别对这些部分进行测试。

1. 测试 Actions

Actions 是用于描述状态变化的普通 JavaScript 对象。可以测试 action 创建函数是否返回正确的 action 对象。

javascript 复制代码
// actions.js
export const increment = () => ({
  type: "INCREMENT",
});

// actions.test.js
import { increment } from "./actions";

describe("increment action", () => {
  test("should return an action with type INCREMENT", () => {
    const action = increment();
    expect(action.type).toBe("INCREMENT");
  });
});
2. 测试 Reducers

Reducers 是纯函数,接收当前状态和 action,返回新的状态。可以测试 reducer 在不同 action 下是否返回正确的状态。

javascript 复制代码
// reducer.js
const initialState = {
  count: 0,
};

const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case "INCREMENT":
      return {
        ...state,
        count: state.count + 1,
      };
    default:
      return state;
  }
};

export default counterReducer;

// reducer.test.js
import counterReducer from "./reducer";

describe("counterReducer", () => {
  test("should handle INCREMENT action", () => {
    const initialState = { count: 0 };
    const action = { type: "INCREMENT" };
    const newState = counterReducer(initialState, action);
    expect(newState.count).toBe(1);
  });

  test("should return the same state for unknown action", () => {
    const initialState = { count: 0 };
    const action = { type: "UNKNOWN_ACTION" };
    const newState = counterReducer(initialState, action);
    expect(newState).toEqual(initialState);
  });
});
3. 测试 Connected Components

如果组件通过 connect 函数连接到 Redux store,可以使用 enzyme@testing-library/react 来测试组件是否正确接收和使用 store 中的状态和 actions。

jsx 复制代码
// CounterComponent.jsx
import React from "react";
import { connect } from "react-redux";
import { increment } from "./actions";

const CounterComponent = ({ count, increment }) => (
  <div>
    <p>Count: {count}</p>
    <button onClick={increment}>Increment</button>
  </div>
);

const mapStateToProps = (state) => ({
  count: state.count,
});

const mapDispatchToProps = {
  increment,
};

export default connect(mapStateToProps, mapDispatchToProps)(CounterComponent);

// CounterComponent.test.js
import React from "react";
import { render, fireEvent, screen } from "@testing-library/react";
import { Provider } from "react-redux";
import configureStore from "redux-mock-store";
import CounterComponent from "./CounterComponent";

const mockStore = configureStore([]);

describe("CounterComponent", () => {
  let store;

  beforeEach(() => {
    store = mockStore({ count: 0 });
  });

  test("should display the count from the store", () => {
    render(
      <Provider store={store}>
        <CounterComponent />
      </Provider>
    );
    const countElement = screen.getByText("Count: 0");
    expect(countElement).toBeInTheDocument();
  });

  test("should dispatch increment action on button click", () => {
    render(
      <Provider store={store}>
        <CounterComponent />
      </Provider>
    );
    const incrementButton = screen.getByText("Increment");
    fireEvent.click(incrementButton);
    const actions = store.getActions();
    expect(actions).toEqual([{ type: "INCREMENT" }]);
  });
});

测试 Recoil

Recoil 是一个用于管理 React 应用状态的库,主要包含 atoms(原子状态)和 selectors(派生状态)。

1. 测试 Atoms

Atoms 是 Recoil 中的基本状态单元。可以测试 atoms 的初始值是否正确。

javascript 复制代码
// atoms.js
import { atom } from "recoil";

export const counterState = atom({
  key: "counterState",
  default: 0,
});

// atoms.test.js
import { counterState } from "./atoms";
import { useRecoilValue } from "recoil";
import { renderHook } from "@testing-library/react-hooks";

describe("counterState", () => {
  test("should have an initial value of 0", () => {
    const { result } = renderHook(() => useRecoilValue(counterState));
    expect(result.current).toBe(0);
  });
});
2. 测试 Selectors

Selectors 是基于 atoms 或其他 selectors 派生出来的状态。可以测试 selectors 是否正确计算派生状态。

javascript 复制代码
// selectors.js
import { atom, selector } from "recoil";

export const counterState = atom({
  key: "counterState",
  default: 0,
});

export const doubleCounterSelector = selector({
  key: "doubleCounterSelector",
  get: ({ get }) => {
    const count = get(counterState);
    return count * 2;
  },
});

// selectors.test.js
import { counterState, doubleCounterSelector } from "./selectors";
import { useRecoilValue, set } from "recoil";
import { renderHook } from "@testing-library/react-hooks";

describe("doubleCounterSelector", () => {
  test("should return double the counter value", () => {
    const { result } = renderHook(() => useRecoilValue(doubleCounterSelector));
    expect(result.current).toBe(0);

    const { set } = renderHook(() => ({
      set: (value) => useSetRecoilState(counterState)(value),
    })).result.current;

    set(5);
    const { result: newResult } = renderHook(() => useRecoilValue(doubleCounterSelector));
    expect(newResult.current).toBe(10);
  });
});
3. 测试 Recoil 组件

可以测试使用 Recoil 状态的组件是否正确更新和渲染。

jsx 复制代码
// CounterComponent.jsx
import React from "react";
import { useRecoilState } from "recoil";
import { counterState } from "./atoms";

const CounterComponent = () => {
  const [count, setCount] = useRecoilState(counterState);

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
};

export default CounterComponent;

// CounterComponent.test.js
import React from "react";
import { render, fireEvent, screen } from "@testing-library/react";
import { RecoilRoot } from "recoil";
import CounterComponent from "./CounterComponent";

describe("CounterComponent", () => {
  test("should display the count and increment on button click", () => {
    render(
      <RecoilRoot>
        <CounterComponent />
      </RecoilRoot>
    );
    const countElement = screen.getByText("Count: 0");
    expect(countElement).toBeInTheDocument();

    const incrementButton = screen.getByText("Increment");
    fireEvent.click(incrementButton);

    const newCountElement = screen.getByText("Count: 1");
    expect(newCountElement).toBeInTheDocument();
  });
});

通过以上方法,可以对 Redux 和 Recoil 状态库进行有效的单元测试,确保状态管理逻辑的正确性。

1106. 单测中,如果有一些三方依赖,想排除这个三方依赖进行测试,该如何做?【热度: 387】【工程化】

关键词:前端单测,如何排除三方依赖

在单元测试里,当存在三方依赖时,为保证测试的独立性与稳定性,你可以采用模拟(Mock)或桩(Stub)的方式排除这些三方依赖。下面结合不同的测试框架和场景,介绍具体的实现方法。

使用 Jest 进行模拟

Jest 是一个功能强大的 JavaScript 测试框架,它内置了模拟功能,可方便地模拟三方依赖。

模拟模块

若三方依赖以模块形式引入,可使用 jest.mock 方法模拟该模块。

示例代码

javascript 复制代码
// api.js (三方依赖模块)
export const fetchData = async () => {
  const response = await fetch("https://example.com/api/data");
  return response.json();
};

// myModule.js (使用三方依赖的模块)
import { fetchData } from "./api";

export const processData = async () => {
  const data = await fetchData();
  return data.length;
};

// myModule.test.js (测试文件)
import { processData } from "./myModule";
jest.mock("./api");
import { fetchData } from "./api";

describe("processData", () => {
  test("should return the length of the data", async () => {
    const mockData = ["item1", "item2", "item3"];
    fetchData.mockResolvedValue(mockData);

    const result = await processData();
    expect(result).toBe(mockData.length);
  });
});

在上述示例中,jest.mock('./api') 模拟了 api.js 模块,fetchData.mockResolvedValue(mockData) 指定了 fetchData 函数的返回值。

模拟全局对象或函数

若三方依赖是全局对象或函数,可使用 jest.spyOn 或直接覆盖该对象或函数。

示例代码

javascript 复制代码
// myFunction.js
export const myFunction = () => {
  return Math.random();
};

// myFunction.test.js
import { myFunction } from "./myFunction";

describe("myFunction", () => {
  test("should return a mocked value", () => {
    const originalMathRandom = Math.random;
    Math.random = () => 0.5;

    const result = myFunction();
    expect(result).toBe(0.5);

    Math.random = originalMathRandom;
  });
});

此例中,通过覆盖 Math.random 函数来模拟其返回值,测试结束后恢复原始函数。

使用 Sinon 进行模拟

Sinon 是一个流行的 JavaScript 测试工具,可创建间谍(spy)、存根(stub)和模拟(mock)对象。

安装 Sinon
bash 复制代码
npm install --save-dev sinon
使用 Sinon 存根函数
javascript 复制代码
// api.js
export const fetchData = async () => {
  const response = await fetch("https://example.com/api/data");
  return response.json();
};

// myModule.js
import { fetchData } from "./api";

export const processData = async () => {
  const data = await fetchData();
  return data.length;
};

// myModule.test.js
import sinon from "sinon";
import { processData } from "./myModule";
import { fetchData } from "./api";

describe("processData", () => {
  let stub;
  beforeEach(() => {
    stub = sinon.stub();
    stub.resolves(["item1", "item2", "item3"]);
    sinon.replace(fetchData, "fetchData", stub);
  });

  afterEach(() => {
    sinon.restore();
  });

  test("should return the length of the data", async () => {
    const result = await processData();
    expect(result).toBe(3);
  });
});

在这个示例中,使用 sinon.stub 创建一个存根函数,使用 sinon.replace 替换 fetchData 函数,测试结束后使用 sinon.restore 恢复原始函数。

通过以上方法,你可以在单元测试中排除三方依赖,确保测试的独立性和稳定性。

相关推荐
QTX1873013 分钟前
使用 Axios 进行 API 请求与接口封装
javascript·vue.js·node.js
9ilk22 分钟前
【前端基础】--- HTML
前端·html
Lafar24 分钟前
Dart单线程怎么保证UI运行流畅
前端·面试
uhakadotcom26 分钟前
BentoML远程代码执行漏洞(CVE-2025-27520)详解与防护指南
后端·算法·面试
_一条咸鱼_30 分钟前
大厂AI 大模型面试:监督微调(SFT)与强化微调(RFT)原理
人工智能·深度学习·面试
不和乔治玩的佩奇30 分钟前
【 设计模式】常见前端设计模式
前端
bloxed36 分钟前
vue+vite 减缓首屏加载压力和性能优化
前端·vue.js·性能优化
打野赵怀真1 小时前
React Hooks 的优势和使用场景
前端·javascript
HaushoLin1 小时前
ERR_PNPM_DLX_NO_BIN No binaries found in tailwindcss
前端·vue.js·css3·html5
Lafar1 小时前
Widget 树和 Element 树和RenderObject树是一一 对应的吗
前端