当前位置: 首页 > news >正文

基于vite创建的react18项目的单元测试

题外话

最近一个小伙伴进了字节外包,第一个活就是让他写一个单元测试。

嗯,说实话,在今天之前我只知道一些理论,但是并没有实操过,于是我就试验了一下。

通过查询资料,大拿们基本都说基于vite的项目,用vitest进行测试比较方便一写。

闲话不多说,步入正题。

1、下载依赖

在vscode终端输入以下命令:

npm install --save-dev vitest @testing-library/react @testing-library/jest-dom
  1. --save-dev: 这个标志表示将这些包添加为开发依赖(devDependencies)。这些依赖只在开发环境中使用,而不会被包含在生产环境中。例如,测试框架和工具通常只在开发时需要,而不需要在生产环境中。

  2. vitest: 这是一个快速的单元测试框架,类似于 Jest,但专为 Vite 生态系统设计。它支持现代 JavaScript 特性,并且与 Vite 无缝集成,非常适合用于测试 Vite 创建的项目。

  3. @testing-library/react: 这是一个用于测试 React 组件的库,提供了一组 API,使得编写测试变得简单而直观。它鼓励以用户的方式来测试组件,而不是实现细节,从而提高测试的可靠性和可维护性。

  4. @testing-library/jest-dom: 这是一个为 Jest 提供的自定义匹配器库,增强了 Jest 的断言功能,使得你可以使用更自然的语法来进行 DOM 相关的断言。例如,你可以使用 toBeInTheDocument() 来检查某个元素是否在文档中,而不需要写复杂的查询逻辑。

2、创建testSetup.js文件

文件里只有一行代码:

import '@testing-library/jest-dom';

3、配置vite.config.js文件

代码如下:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'// https://vitejs.dev/config/
export default defineConfig({plugins: [react()],test: {globals: true,environment: 'jsdom',setupFiles: './testSetup.js',},
})
  • test: 这是 Vitest 的配置部分。
  • globals: true: 这个选项表示在测试文件中可以使用全局的测试函数,比如 describetestexpect 等,而不需要每次都导入它们。
  • environment: 'jsdom': 这个选项指定测试运行在 jsdom 环境中。jsdom 是一个 JavaScript 实现的 DOM,用于模拟浏览器环境,这样你可以在 Node.js 中运行测试并且测试涉及 DOM 操作的代码。
  • setupFiles: './testSetup.js': 这个选项指定一个设置文件,在测试运行之前会执行。在第二步中我只是引入了一个包。

4、添加脚本

在项目的 package.json 中添加测试脚本:

"scripts": {"test": "vitest"
}

5、创建测试文件

在你的组件目录下,创建一个与组件同名的测试文件,通常以 .test.tsx 结尾。例如,如果你有一个 Wjllogin.jsx 组件,你可以在同一目录下创建 Wjllogin.test.jsx

我的demo中的Wjllogin.jsx中的代码如下:

import React, { useState } from "react"; // 导入 React 和 useState Hook
import "./wjs.scss"; // 导入样式文件
import "animate.css"; // 导入动画效果库
import { wjllogin } from "../axiosAPI/wjl"; // 导入用于登录的 API 函数
import { useNavigate } from "react-router-dom"; // 导入路由导航 Hook
import { message } from "antd"; // 导入 Ant Design 的消息提示组件// 定义 Wjllogin 组件
export default function Wjllogin() {let navigate = useNavigate(); // 初始化路由导航let [name, setName] = useState(""); // 定义状态变量 name 和更新函数 setNamelet [card, setCard] = useState(""); // 定义状态变量 card 和更新函数 setCardlet [tid, setTid] = useState(""); // 定义状态变量 tid 和更新函数 setTidconst [messageApi, contextHolder] = message.useMessage(); // 使用 Ant Design 的消息提示 API// 定义登录函数let login = async () => {// 调用 wjllogin API 进行登录let {data: { code, sid, clazz, sname }, // 解构 API 返回的数据} = await wjllogin({ name, card, tid }); // 传递姓名、身份证号和学号到 API// 检查返回的状态码if (code === 200) {// 登录成功,保存用户信息到 sessionStoragesessionStorage.setItem("sid", sid); // 保存会话 IDsessionStorage.setItem("clazz", clazz); // 保存班级信息sessionStorage.setItem("token", "token"); // 保存 token(这里是示例,实际应从 API 获取)sessionStorage.setItem("sname", sname); // 保存姓名// 显示成功消息messageApi.open({type: "success",content: "登录成功, 即将跳转至主页",});// 设置延迟后跳转到主页setTimeout(() => {navigate("/wjlhome"); // 跳转到主页}, 2000);} else {// 登录失败,显示错误消息messageApi.open({type: "error",content: "登录失败,请检查姓名、身份证号或学号",});}};// 组件的 JSX 结构return (<div className="examlogin animate__animated animate__slideInLeft">{" "}{/* 主容器,包含动画效果 */}<div className="top">{" "}{/* 顶部区域 */}<imgclassName="main"src="https://cdn7.axureshop.com/demo/2001850/images/%E5%9C%A8%E7%BA%BF%E8%80%83%E8%AF%95/u2853.svg"alt=""/>{" "}{/* 主图标 */}<imgclassName="x"src="https://cdn7.axureshop.com/demo/2001850/images/%E5%9C%A8%E7%BA%BF%E8%80%83%E8%AF%95/u2854.svg"alt=""/>{" "}{/* 副图标 */}</div><div className="title">{" "}{/* 标题区域 */}培训学院在线考试系统 {/* 系统名称 */}<span className="lessfont">考生版</span> {/* 子标题 */}</div><div className="form">{" "}{/* 表单区域 */}<p><label htmlFor="name">考生姓名:</label><inputid="name"value={name}onChange={(e) => setName(e.target.value)}/></p><p><label htmlFor="card">身份证号:</label><inputid="card"type="text" // 输入框类型value={card} // 绑定到 card 状态onChange={(e) => {setCard(e.target.value); // 更新 card 状态}}/></p><p><label htmlFor="tid">学号:</label> {/* 学号标签 */}<inputid="tid"type="text" // 输入框类型value={tid} // 绑定到 tid 状态onChange={(e) => {setTid(e.target.value); // 更新 tid 状态}}/></p><p><buttonclassName="btn" // 按钮样式onClick={() => {login(); // 点击按钮时调用 login 函数}}>登录 {/* 按钮文本 */}</button></p>{contextHolder} {/* 显示消息提示的容器 */}</div></div>);
}

测试代码Wjllogin.test.jsx中的代码如下:

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom'; // 导入 MemoryRouter
import Wjllogin from './Wjllogin'; // 根据你的文件路径调整
import { wjllogin } from '../axiosAPI/wjl'; // 导入登录 API
import { describe, it, beforeEach, vi } from 'vitest'; // 导入 Vitest 的函数// Mock the API call
vi.mock('../axiosAPI/wjl', () => ({wjllogin: vi.fn(),
}));describe('Wjllogin Component', () => {beforeEach(() => {// 清除所有的 mocksvi.clearAllMocks();});it('renders Wjllogin component', () => {render(<MemoryRouter><Wjllogin /></MemoryRouter>);// Check if elements are renderedexpect(screen.getByText(/考生姓名:/)).toBeInTheDocument();expect(screen.getByText(/身份证号:/)).toBeInTheDocument();expect(screen.getByText(/学号:/)).toBeInTheDocument();expect(screen.getByRole('button', { name: /登录/i })).toBeInTheDocument();});it('successful login', async () => {// Mock the API response for a successful loginwjllogin.mockResolvedValueOnce({data: { code: 200, sid: '123', clazz: 'A1', sname: 'John Doe' },});render(<MemoryRouter><Wjllogin /></MemoryRouter>);// Fill in the input fieldsfireEvent.change(screen.getByLabelText(/考生姓名:/), { target: { value: 'John Doe' } });fireEvent.change(screen.getByLabelText(/身份证号:/), { target: { value: '123456789012345678' } });fireEvent.change(screen.getByLabelText(/学号:/), { target: { value: '2023001' } });// Click the login buttonfireEvent.click(screen.getByRole('button', { name: /登录/i }));// Wait for the success message to appearawait waitFor(() => {expect(screen.getByText(/登录成功, 即将跳转至主页/)).toBeInTheDocument();});// Check if sessionStorage is set (you may need to mock sessionStorage)expect(sessionStorage.getItem('sid')).toBe('123');expect(sessionStorage.getItem('clazz')).toBe('A1');expect(sessionStorage.getItem('sname')).toBe('John Doe');});it('failed login', async () => {// Mock the API response for a failed loginwjllogin.mockResolvedValueOnce({data: { code: 400 },});render(<MemoryRouter><Wjllogin /></MemoryRouter>);// Fill in the input fieldsfireEvent.change(screen.getByLabelText(/考生姓名:/), { target: { value: 'Invalid User' } });fireEvent.change(screen.getByLabelText(/身份证号:/), { target: { value: '000000000000000000' } });fireEvent.change(screen.getByLabelText(/学号:/), { target: { value: '0000000' } });// Click the login buttonfireEvent.click(screen.getByRole('button', { name: /登录/i }));// Wait for the error message to appearawait waitFor(() => {expect(screen.getByText(/登录失败,请检查姓名、身份证号或学号/)).toBeInTheDocument();});});
});

解释代码

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom'; // 导入 MemoryRouter
import Wjllogin from './Wjllogin'; // 根据你的文件路径调整
import { wjllogin } from '../axiosAPI/wjl'; // 导入登录 API
import { describe, it, beforeEach, vi } from 'vitest'; // 导入 Vitest 的函数
  • @testing-library/react: 这个库提供了一些函数,用于渲染组件和进行交互测试。
  • MemoryRouter: 这是 React Router 提供的一个组件,用于在测试中模拟路由环境。
  • Wjllogin: 这是被测试的组件,假设它是一个登录表单。
  • wjllogin: 这是一个从 API 模块导入的函数,用于处理登录请求。
  • describeitbeforeEachvi: 这些是 Vitest 提供的函数,用于组织测试用例和创建 mock 函数。
vi.mock('../axiosAPI/wjl', () => ({wjllogin: vi.fn(),
}));
  • 这里使用 vi.mock 来模拟 wjllogin 函数,以便在测试中控制其返回值,而不实际调用 API。
describe('Wjllogin Component', () => {}
  • describe 用于将相关的测试用例组织在一起,便于管理和阅读。
beforeEach(() => {vi.clearAllMocks();
});
  • beforeEach 在每个测试用例执行之前调用,确保每个测试用例都在干净的状态下运行,避免测试之间的相互影响。

测试用例

it('renders Wjllogin component', () => {render(<MemoryRouter><Wjllogin /></MemoryRouter>);// Check if elements are renderedexpect(screen.getByText(/考生姓名:/)).toBeInTheDocument();expect(screen.getByText(/身份证号:/)).toBeInTheDocument();expect(screen.getByText(/学号:/)).toBeInTheDocument();expect(screen.getByRole('button', { name: /登录/i })).toBeInTheDocument();
});
  • 这个测试用例检查 Wjllogin 组件是否能够正确渲染,并且确保特定的文本和登录按钮存在于文档中。
it('successful login', async () => {// Mock the API response for a successful loginwjllogin.mockResolvedValueOnce({data: { code: 200, sid: '123', clazz: 'A1', sname: 'John Doe' },});render(<MemoryRouter><Wjllogin /></MemoryRouter>);// Fill in the input fieldsfireEvent.change(screen.getByLabelText(/考生姓名:/), { target: { value: 'John Doe' } });fireEvent.change(screen.getByLabelText(/身份证号:/), { target: { value: '123456789012345678' } });fireEvent.change(screen.getByLabelText(/学号:/), { target: { value: '2023001' } });// Click the login buttonfireEvent.click(screen.getByRole('button', { name: /登录/i }));// Wait for the success message to appearawait waitFor(() => {expect(screen.getByText(/登录成功, 即将跳转至主页/)).toBeInTheDocument();});// Check if sessionStorage is set (you may need to mock sessionStorage)expect(sessionStorage.getItem('sid')).toBe('123');expect(sessionStorage.getItem('clazz')).toBe('A1');expect(sessionStorage.getItem('sname')).toBe('John Doe');
});
  • 这个测试用例模拟了一个成功的登录过程。它首先设置了 wjllogin 函数的返回值,然后渲染组件,填写表单,点击登录按钮,并最终检查成功消息是否出现以及 sessionStorage 是否正确设置。
it('failed login', async () => {// Mock the API response for a failed loginwjllogin.mockResolvedValueOnce({data: { code: 400 },});render(<MemoryRouter><Wjllogin /></MemoryRouter>);// Fill in the input fieldsfireEvent.change(screen.getByLabelText(/考生姓名:/), { target: { value: 'Invalid User' } });fireEvent.change(screen.getByLabelText(/身份证号:/), { target: { value: '000000000000000000' } });fireEvent.change(screen.getByLabelText(/学号:/), { target: { value: '0000000' } });// Click the login buttonfireEvent.click(screen.getByRole('button', { name: /登录/i }));// Wait for the error message to appearawait waitFor(() => {expect(screen.getByText(/登录失败,请检查姓名、身份证号或学号/)).toBeInTheDocument();});
});
  • 这个测试用例模拟了一个失败的登录过程。它设置了 wjllogin 函数的返回值为一个错误代码,然后填写不正确的表单数据,点击登录按钮,并最终检查错误消息是否出现。

这段代码为 Wjllogin 组件提供了全面的测试,包括组件的渲染、成功登录和失败登录的场景。通过使用 Vitest 和 React Testing Library,测试用例能够模拟用户交互并验证组件的行为是否符合预期。 

6、运行测试

通过在终端运行命令 npx vitser 看结果

npx vitest

这里三个测试用例全部通过,说明代码编写没有问题。

相关文章:

基于vite创建的react18项目的单元测试

题外话 最近一个小伙伴进了字节外包&#xff0c;第一个活就是让他写一个单元测试。 嗯&#xff0c;说实话&#xff0c;在今天之前我只知道一些理论&#xff0c;但是并没有实操过&#xff0c;于是我就试验了一下。 通过查询资料&#xff0c;大拿们基本都说基于vite的项目&…...

fiddler抓包工具与requests库构建自动化报告

一. Fiddler 抓包工具 1.1 Fiddler 工具介绍和安装 Fiddler 是一款功能强大的 HTTP 调试代理工具&#xff0c;能够全面记录并深入检查您的计算机与互联网之间的 HTTP 和 HTTPS 通信数据。其主界面布局清晰&#xff0c;主要包含菜单栏、工具栏、树形标签栏和内容栏。 1.2 Fid…...

Docker login 报证书存储错误的解决办法

文章目录 docker login 出现错误&#xff0c;提示&#xff1a;Error saving credentials: error storing credentials - err: exit status 1, out: Cannot autolaunch D-Bus without X11 $DISPLAY 环境 使用的是 Mint Linux &#xff0c;容器为 docker-ce 最新版 1 2 3 4 $…...

【自动化Selenium】Python 网页自动化测试脚本(上)

目录 1、Selenium介绍 2、Selenium环境安装 3、创建浏览器、设置、打开 4、打开网页、关闭网页、浏览器 5、浏览器最大化、最小化 6、浏览器的打开位置、尺寸 7、浏览器截图、网页刷新 8、元素定位 9、元素交互操作 10、元素定位 &#xff08;1&#xff09;ID定位 &…...

什么是MyBatis?

MyBatis简介 MyBatis是一款优秀的持久层框架&#xff0c;用于简化Java应用程序对数据库的操作。它曾是Apache的一个开源项目&#xff0c;名为iBatis&#xff0c;2010年迁移到Google Code并改名为MyBatis&#xff0c;2013年11月又迁移到了GitHub。 一、MyBatis的作用 在JavaE…...

TortoiseGit 将本地已有仓库推送到远程

TortoiseGit 将本地已有仓库推送到远程 一、创建线上仓库二、创建本地仓库三、提交内容到本地仓库四、添加远程仓库地址补充 一、创建线上仓库 在gitlab管理面页面按这前讲过的步骤创建一个空仓库。&#xff08;通常我们把服务器上这个仓库叫远程仓库&#xff0c;把我们自己电…...

腾讯云OCR车牌识别实践:从图片上传到车牌识别

在当今智能化和自动化的浪潮中&#xff0c;车牌识别&#xff08;LPR&#xff09;技术已经广泛应用于交通管理、智能停车、自动收费等多个场景。腾讯云OCR车牌识别服务凭借其高效、精准的识别能力&#xff0c;为开发者提供了强大的技术支持。本文将介绍如何利用腾讯云OCR车牌识别…...

TailwindCss 总结

目录 一、简介 二、盒子模型相关 三、将样式类写到一个类里面apply 四、一款TailWind CSS的UI库 一、简介 官方文档&#xff1a;Width - TailwindCSS中文文档 | TailwindCSS中文网 Tailwind CSS 的工作原理是扫描所有 HTML 文件、JavaScript 组件以及任何 模板中的 CSS 类…...

Java与C#

Java和C#&#xff08;C Sharp&#xff09;是两种流行的面向对象编程语言&#xff0c;它们在很多方面非常相似&#xff0c;因为它们都受到了类似的编程范式和语言设计理念的影响。然而&#xff0c;它们之间也存在一些重要的区别。 平台依赖性&#xff1a; Java&#xff1a;Java是…...

leetcode:222完全二叉树的节点个数

给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二叉树 的定义如下&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没填满外&#xff0c;其余每层节点数都达到最大值&#xff0c;并且最下面一层的节点都集中在该层最左边的若干位置。若最…...

[STM32]从零开始的STM32 FreeRTOS移植教程

一、前言 如果能看到这个教程的话&#xff0c;说明大家已经学习嵌入式有一段时间了。还记得嵌入式在大多数时候指的是什么吗&#xff1f;是的&#xff0c;我们所说的学习嵌入式大部分时候都是在学习嵌入式操作系统。从简单的一些任务状态机再到复杂一些的RTOS&#xff0c;再到最…...

java——Tomcat连接池配置NIO、BIO、APR

Tomcat连接池的配置涉及不同的IO模型&#xff0c;包括NIO&#xff08;Non-blocking IO&#xff0c;非阻塞IO&#xff09;、APR&#xff08;Apache Portable Runtime&#xff0c;Apache可移植运行库&#xff09;和BIO&#xff08;Blocking IO&#xff0c;阻塞IO&#xff09;。以…...

跨域相关的一些问题 ✅

当网页从一个源&#xff08;https://baidu.com&#xff09;请求另一个源&#xff08;如 https://taobao/api&#xff09;的资源时&#xff0c;就发生了跨域。由于安全原因&#xff08;防止恶意网站通过脚本访问用户在其他网站上的数据&#xff09;&#xff0c;浏览器对跨域请求…...

RPC学习

一、什么是 RPC RPC&#xff08;Remote Procedure Call&#xff09;&#xff0c;即远程过程调用&#xff0c;是一种计算机通信协议&#xff0c;它允许运行在一台计算机上的程序调用另一台计算机上的子程序或函数&#xff0c;就好像调用本地程序中的函数一样&#xff0c;无需程序…...

coe文件转mif(c语言)

1 mif文件格式 DEPTH=1024; --The size of data in bits WIDTH=16; --The size of memory in words ADDRESS_RADIX = DEC; --The radix for address values DATA_RADIX = UNS...

【leetcode】动态规划

31. 873. 最长的斐波那契子序列的长度 题目&#xff1a; 如果序列 X_1, X_2, ..., X_n 满足下列条件&#xff0c;就说它是 斐波那契式 的&#xff1a; n > 3对于所有 i 2 < n&#xff0c;都有 X_i X_{i1} X_{i2} 给定一个严格递增的正整数数组形成序列 arr &#xff0…...

介绍一下atoi(arr);(c基础)

hi , I am 36 适合对象c语言初学者 atoi(arr)&#xff1b;是返回整数(int型)&#xff0c;整数是arr数组中字符中数字 格式 #include<stdio.h> atoi(arr); 返回值arr数组中的数字 未改变arr数组 #include<stdlib.h>//atoi(arr); 返 <stdlib> int main(…...

docker入门学习笔记

docker的定义 docker是一个用于构建、运行、传送 应用程序的平台。 为什么要使用docker &#xff1f; 在开发测试库环境中测试成功后&#xff0c;打包成集装箱&#xff0c;到生产环境也是能够成功的。而传统的安装方式不仅繁琐&#xff0c;并且在测试环境安装后&#xff0c;到…...

使用Python和Pybind11调用C++程序(CMake编译)

目录 一、前言二、安装 pybind11三、编写C示例代码四、结合Pybind11和CMake编译C工程五、Python调用动态库六、参考 一、前言 跨语言调用能对不同计算机语言进行互补&#xff0c;本博客主要介绍如何实现Python调用C语言编写的函数。 实验环境&#xff1a; Linux gnuPython3.10…...

tableau-制作30个图表

制作条形图 步骤: 1、横轴是数值,对应了某一个度量值,纵轴是一个标签 战区的成交额,条形图横轴是战区,纵轴是成交额 下钻条形图 1、增加业务架构-战区右键点击,分层结构,增加分层结构 调整业务架构,将战区,城市,小组移动到业务架构下方 此时的条形图上方有➕号展开后…...

stm32G473的flash模式是单bank还是双bank?

今天突然有人stm32G473的flash模式是单bank还是双bank&#xff1f;由于时间太久&#xff0c;我真忘记了。搜搜发现&#xff0c;还真有人和我一样。见下面的链接&#xff1a;https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

反向工程与模型迁移:打造未来商品详情API的可持续创新体系

在电商行业蓬勃发展的当下&#xff0c;商品详情API作为连接电商平台与开发者、商家及用户的关键纽带&#xff0c;其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息&#xff08;如名称、价格、库存等&#xff09;的获取与展示&#xff0c;已难以满足市场对个性化、智能…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案&#xff0c;允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...

C++八股 —— 单例模式

文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全&#xff08;Thread Safety&#xff09; 线程安全是指在多线程环境下&#xff0c;某个函数、类或代码片段能够被多个线程同时调用时&#xff0c;仍能保证数据的一致性和逻辑的正确性&#xf…...

Spring Security 认证流程——补充

一、认证流程概述 Spring Security 的认证流程基于 过滤器链&#xff08;Filter Chain&#xff09;&#xff0c;核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤&#xff1a; 用户提交登录请求拦…...