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

单元测试工具JEST入门——纯函数的测试

单元测试工具JEST入门——纯函数的测试

  • 什么是测试❓
  • 🙉 我只是开发而已?
  • 常见单元测试工具 🔧
  • jest的使用
    • 👀 首先你得知道
    • 一个简单的例子🌰
    • 😨 Oops!出现了一些问题
    • 👏 高效的持续监听!
    • 生成测试覆盖率报告 📃
    • Jest 中的 mock 💡
    • Jest 中的钩子函数 🌟
    • 常用的断言方法
    • Mock Timer
    • 什么是快照测试?
  • 前端单元测试策略与原则
  • ⭕ 你可能会遇到的问题
  • Demo

什么是测试❓

对于前端来说,测试主要是对HTML、CSS、JavaScript进行测试,以确保代码的正常运行。

常见的测试:单元测试、集成测试、端到端的测试(e2e)

  • 单元测试:对程序中最小可测试单元进行测试。——零件测试
  • 集成测试:对多个可执行单元组成的整体进行测试。——零件组装成的“发动机”测试
  • 端到端的测试:从服务端到客户端的测试,是对整体系统进行测试。——执行完整流程。

测试方式:人工测试、自动测试

  • 人工测试:测试同学根据业务流程进行操作,人工检查哪一步会出现问题。它只能测试出看得见的问题,对于看不见的部分,例如内部函数、逻辑代码等,都有可能会出现问题。
  • 自动测试:利用写好的代码对代码进行测试。能够弥补人工测试的不足,它的颗粒度是代码级别的,能够准确识别某个方法的错误。
    在实际的开发过程中一般采用人工测试+自动测试的方式,力求100%覆盖测试目标。

🙉 我只是开发而已?

bug发现在开发阶段成本很低,如果发现在生产环境成本很高。

技术的角度:有效的提高代码的健壮性,有效的增加代码的可维护性,对于后期的代码重构是必要条件。
团队的角度:可以有效的减少测试成本,维护成本。

单元测试是前期慢,后期快的工作,做好单元测试可以大大缩短后期的测试和改 bug 的时间。

单元测试的好处

  1. 更少的调试:经过测试的代码在提交时有较少的缺陷
  2. 增加对变化的信心:可以自信地审查和接受项目的变化
  3. 更简单的审查:CR中验证代码是否符合预期的精力就会减少
  4. 深思熟虑的设计:编写测试是锻炼代码本身的一种实用手段
  5. 快速、高质量地发布:可以放心地发布新版本的应用程序

❌ 因为单元测试有这些好处,所以要做单元测试??

✅ 如果不做单元测试,会有什么样的问题?
在这里插入图片描述

瀑布流开发:项目的每个阶段都会带来种种需求不匹配的情况,导致交付的最终价值可能不是客户所需要的。

敏捷开发:迭代式的软件开发流程,在每一个小的迭代周期内,走过完整的流程,并及时发布一个可用版本给用户,风险提前暴露,用户及时反馈,快速进入下一个迭代,降低需求不匹配带来的风险。

敏捷是为了更快交付有价值的、可工作的软件。

市场变化多端,我们需要及时推出产品,从而验证产品在市场上的用户反馈。

💡 想法付诸行动 → 💯 业务取得成效:决策、设计、开发、测试、发布、反馈… …

戴明环:是全面质量管理的思想基础和方法,将质量管理分为四个阶段,即Plan(计划)、Do(执行)、Check(检查)和 Action(处理)。

不断缩短反馈周期,提高反馈速度,才能减少不必要的浪费。

Q:“敏捷交付最重要的是什么?” A:“快。快速迭代、持续交付用户价值。”
Q:“对我们开发者有什么影响?” A:“如果我们不写单元测试,我们就快不起来。”

每次开发上线,团队都要投入人力来进行手工测试,其中也包括你自己 😢
因为没有测试,不敢随意重构,从而导致代码逐渐腐化,随着项目的进展,代码越来越复杂,复杂的代码会导致开发速度降低,从而陷入死循环。代码越来越烂、开发越来越慢、BUG越来越多。
除此之外,我们整个项目的人员会流动,应用会变得越来越复杂,功能会变得越来越多,那么人员一定会流动,需求一定会增加,直到整个项目没有一个人可以了解到应用的所有功能,那么对软件进行修改的成本就会非常高。
如果试图依赖人工方式来应对快速变化的市场,挑战是非常高的。而单元测试是自动化的,能够给我们极快的反馈速度。

所以,单元测试是非常有必要的!

常见单元测试工具 🔧

目前用的最多的前端单元测试框架主要有 Mocha、Jest,但推荐使用 Jest,因为 Jest 和 Mocha 相比,无论从 github stats & issues 量,npm下载量相比,都有明显优势。
在这里插入图片描述

👉 详见:github stats 以及 npm 下载量的实时数据

jest的使用

Jest 是 Facebook 开发的一款 JavaScript 测试框架,在 Facebook 内部广泛用来测试各种 JavaScript 代码。

  • 轻松上手
  • 高性能支持测试的并发运行
  • 内置强大的断言与 mock 功能
  • 内置测试覆盖率统计功能
  • 内置 Snapshot(快照)机制
npm install --save-dev jest

👀 首先你得知道

Jest 的测试脚本名形如*.test.js,不论 Jest 是全局运行还是通过 npm test 运行,它都会执行当前目录下所有的*.test.js 或 *.spec.js 文件完成测试

  • test(name, fn, timeout)是将运行测试的方法。也叫 it(name, fn, timeout)
  • describe(name, fn)是一个将多个相关的测试组合在一起的块。
// add.js
function add(a, b) {return a + b
}// add.test.js
describe('add function', () => {test('adds 1 + 2 to equal 3', () => {const result = add(1, 2)expect(result).toBe(3)})it('adds 5 + 7 to equal 12', () => {const result = add(5, 7)expect(result).toBe(12)})
})

一个简单的例子🌰

// sum.js
function sum(a, b) {return a + b
}
module.exports = sum// sum.test.js
import { expect, test } from '@jest/globals'
const sum = require('./sum')test('adds 1 + 2 to equal 3', () => {expect(sum(1, 2)).toBe(3)
})// package.json
{"scripts": {"test": "jest"},
}

运行yarn test 或者 npm test

😨 Oops!出现了一些问题

当我想用 import 来引入时,出现了这样的问题:

SyntaxError: Cannot use import statement outside a module

原因:nodejs 采用的是 CommonJS 的模块化规范,使用 require 引入模块;而 import 是 ES6 的模块化规范关键字。想要使用 import,必须引入 babel 转义支持,通过 babel 进行编译,使其变成 node 的模块化代码:

npm install --save-dev @babel/core @babel/preset-env

项目的根目录 .babelrc.js

module.exports = {presets: ['@babel/preset-env']
}

🎉 问题解决!
原因:jest 运行时内部先执行( jest-babel ),检测是否安装 babel-core,然后取 .babelrc 中的配置运行测试之前结合 babel 先把测试用例代码转换一遍然后再进行测试

如果我想测试ts?
jest只能测试 js 文件, 要对其他类型的文件进行测试,则需要使用其他的扩展,在typescript项目中,我们可以使用babel或者ts-jest来实现项目对ts测试的支持。

第一种:使用 babel:

// 安装依赖
npm install --save-dev @babel/preset-typescript// 项目的根目录 .babelrc.js
presets: ['@babel/preset-typescript', '@babel/preset-env']

第二种:使用 ts-jest:

// 安装依赖
npm add --save-dev jest ts-jest @types/jest

区别:在对 Typescript 的测试中,因为Babel对 Typescrip 的支持是纯编译形式(无类型校验),所以@babel/preset-typescript 并不能 ts 类型进行检查,所以如果需要类型校验,你就需要使用 ts-jest 来进行 Typescrip的支持。
详见:使用Typescript

👏 高效的持续监听!

为了提高效率,可以通过加启动参数的方式让 jest 持续监听文件的修改,而不需要每次修改完再重新执行测试用例,在package.json中:

"test": "jest --watchAll"

生成测试覆盖率报告 📃

什么是单元测试覆盖率?
指在所有功能代码中,完成了单元测试的代码所占的比例。
单元测试覆盖率 = 被测代码行数 / 参测代码总行数 * 100%

两种方法:

  1. 命令执行:
npm test --coverage
  1. 在 jest.config.js 中配置:
module.exports = {// 是否显示覆盖率报告collectCoverage: true,// 告诉 jest 哪些文件需要经过单元测试测试collectCoverageFrom: ['src/utils/**/*'],
}

这里我以utils下的所有文件为例:
在这里插入图片描述

参数名含义说明
% Stmts语句覆盖率是否每条语句都执行了?
% Branch分支覆盖率是否每个情况分支都执行了?(例如 if-else)
% Funcs函数覆盖率是否每个函数都调用了?
% Lines行覆盖率是否每一行都执行了?
Uncobered Line未覆盖到的行未覆盖到的行数

Jest 中的 mock 💡

为什么Jest需要模拟函数?

世界软件开发大师Martin Fowler根据是否依赖其他模块将单元测试分为了社交型测试单元独立型测试单元

在测试中我们特别需要注意不同模块之间的依赖:

  • Database数据库
  • Network Requests网络请求
  • Access to Files存取文件
  • Any External System任何外部系统

这些依赖我们需要“扮演者”,也就是模拟函数 🙋

这里我们主要了解Jest 中的三个与 Mock 函数相关的API,分别是 jest.fn()、jest.mock()、jest.spyOn()

  1. 模拟函数 jest.fn()

jest.fn(implementation) :是创建Mock函数最简单的方式,用于模拟特定行为。

我们可以设置该函数的返回值、监听该函数的调用、改变函数的内部实现等等,我们通过 jest.fn() 创建的函数有一个特殊的 .mock 属性,该属性保存了每一次调用情况。

写一个单元测试:

export const myMap = (arr, fn) => {return arr.map(fn)
}

如代码中的单元测试所示,只需要判断一下函数的返回结果即可。

import { myMap } from './myMap'
it('测试 map方法', () => {// 自定义方法const fn = (item) => item * 2expect(myMap([1, 2, 3], fn)).toEqual([2, 4, 6])
})

🤔 问题:那如果我需要更细致地去判断每一次调用传进去的函数是否是数组中的每一项,或者函数是否被调用了三次,那该怎么写单元测试?

import { myMap } from './myMap'
it('测试 map 方法', () => {// 通过jest.fn声明的函数可以被追溯const fn = jest.fn((item) => (item *= 2))expect(myMap([1, 2, 3], fn)).toEqual([2, 4, 6])// 调用3次expect(fn.mock.calls.length).toBe(3)// 每次函数返回的值是 2,4,6expect(fn.mock.results.map((item) => item.value)).toEqual([2, 4, 6])// 打印 fn.mockconsole.log(fn.mock)
})

打印结果
[图片]

如果没有定义函数内部的实现,jest.fn() 会返回 undefined 作为返回值。

it('测试返回 undefined', () => {const myFn = jest.fn()const result = myFn({ a: 1 })// undefinedconsole.log(result)
})

还可以设置返回值,定义内部实现或返回Promise对象。

describe('test', () => {it('测试设置返回值', () => {const myFn = jest.fn().mockReturnValue('fffff')expect(myFn()).toBe('fffff')})it('测试定义内部实现', () => {const myFn = jest.fn((a, b) => a * b)expect(myFn(10, 20)).toBe(200)})it('测试返回Promise对象', async () => {const asyncMock = jest.fn().mockResolvedValue('default')const result = await asyncMock()expect(Object.prototype.toString.call(asyncMock())).toBe('[object Promise]')})
})
  1. 模拟文件jest.mock()

jest.mock(moduleName, factory, options):用来mock一些模块或者文件

// service.js
import { getNames } from '../common/service'
export const searchNames = async (keyword) => {// 获取接口数据const namesList = await getNames()return namesList.filter((item) => item.includes(keyword))
}// service.test.js
jest.mock('../common/service', () => ({getNames: jest.fn(() => ['Jack', 'Rose'])
}))test('find target result', () => {const keyword = 'Jack'const result = searchNames(keyword)expect(result).toEqual(['Jack'])
})
  1. jest.spyOn()

jest.spyOn(object, methodName):用来创建一个被监视(spied)的函数,返回一个mock function,和 jest.fn 相似,但是能够追踪object[methodName]的调用信息。

// multiply.js
export const math = {multiply: (a, b) => {return a + b},
}// multiply.test.js
import { math } from './multiply'it('should spy on add function', () => {// 创建一个被监视的函数const spy = jest.spyOn(math, 'multiply')// 执行测试逻辑const result = math.multiply(2, 3)// 验证函数是否被调用expect(spy).toHaveBeenCalled()// 验证函数的返回值expect(result).toBe(5)// 清除监视器spy.mockRestore()
})

Jest 中的钩子函数 🌟

  • beforeAll(fn, timeout):所有测试之前执行。 设置一些在测试用例之间共享的全局状态。
  • afterAll(fn, timeout):所有测试执行完之后。 清理一些在测试用例之间共享的全局状态。
  • beforeEach(fn, timeout):每个测试实例之前执行。 重新设置一些全局状态在每个测试开始前。
  • afterEach(fn, timeout):每个测试实例完成之后执行。 清理一些在每个测试中创建的临时状态。

如果传入的回调函数返回值是 promise 或者 generator,Jest 会等待 promise resolve 再继续执行。
可选地传入第二个参数 timeout(毫秒) 指定函数执行超时时间。 The default timeout is 5 seconds。

// counter.ts
let counter = 0
export function increment() {counter++
}
export function decrement() {counter--
}
export function getCounter() {return counter
}// counter.test.ts
describe('Counter functions', () => {// beforeAll: 在所有测试用例运行之前执行一次beforeAll(() => {console.log('Before all tests')})// afterAll: 在所有测试用例运行之后执行一次afterAll(() => {console.log('After all tests')})// beforeEach: 在每个测试用例运行之前执行beforeEach(() => {console.log('Before each test')increment() // 在每个测试用例前对计数器进行递增})// afterEach: 在每个测试用例运行之后执行afterEach(() => {console.log('After each test')decrement() // 在每个测试用例后对计数器进行递减})it('increments the counter', () => {console.log(getCounter())})it('decrements the counter', () => {console.log(getCounter())})
})

常用的断言方法

仅列举常用方法,更多内容详见:Jest 官网 API

.not 修饰符允许你测试结果不等于某个值的情况
.toHaveLength 可以很方便的用来测试字符串和数组类型的长度是否满足预期
.toThorw 能够让我们测试被测试方法是否按照预期抛出异常
.toMatch 传入一个正则表达式,它允许我们来进行字符串类型的正则匹配
.toContain 匹配对象中是否包含

检查一些特殊的值(null,undefined 和 boolean)
toBeNull 仅匹配 null
toBeUndefined 仅匹配 undefined
toBeDefined 与…相反 toBeUndefined
toBeTruthy 匹配 if 语句视为 true 的任何内容
toBeFalsy 匹配 if 语句视为 false 的任何内容

检查数字类型(number)
toBeGreaterThan 大于
toBeGreaterThanOrEqual 大于等于
toBeLessThan 小于
toBeLessThanOrEqual 小于等于
toBeCloseTo 用来匹配浮点数(带小数点的相等)

Mock Timer

假如现在有一个函数 afterTime,它的作用是在 2000ms 后执行传入的 callback

export const afterTime = (callback) => {console.log('准备计时')setTimeout(() => {console.log('时间到了')callback && callback()}, 2000)
}

如果不 Mock 时间,那么我们就得写这样的用例:

test('wait time', (callback) => {afterTime(() => {callback()expect(undefined)})
})

这样我们得死等 2000 毫秒才能跑这完这个用例,这非常不合理。

先用 jest.useFakeTimers Mock 定时器,并监听 setTimeout。mock一个 callback 函数,执行 afterTime 后, 对 callback 的调用做了一些断言。

describe('afterTime', () => {beforeAll(() => {// mock定时器jest.useFakeTimers()})test('fast', () => {// 监听setTimeoutjest.spyOn(global, 'setTimeout')// mock一个函数const callback = jest.fn()// 执行afterTimeafterTime(callback)// 此时断言这个函数没有被调用expect(callback).not.toHaveBeenCalled()// 快进时间jest.runAllTimers()// 断言这个函数被调用了expect(callback).toHaveBeenCalled()})
})

什么是快照测试?

直接上代码:

// searchName.ts
export const sum = (a, b) => a + b// searchName.test.js
test('sum', () => {expect(sum(1, 3)).toMatchInlineSnapshot()
})

运行npm test后,发现:代码中自动出现了 expect(sum(1, 3)).toMatchInlineSnapshot(4)

但是当我们随意更改sum方法的参数时,你会发现报错了:
在这里插入图片描述
它需要我们执行npm test -- -u来更新它。

明明代码没有问题,只是修改了传参,测试却出错了,这就是测试中的“假错误”。虽然普通的单测中也有可能会出现“假错误”,但是快照测试中出现的概率更高,这也是很多人不信任快照测试的主要原因。

当我们改成使用toMatchSnapshot方法后,发现当前文件夹下出现了.snap文件,打开可以看到toMatchSnapshot将结果放在了这个文件里。
在这里插入图片描述

💡 对于那种输出很复杂,而且不方便用 expect 做断言时,快照测试是一个好方法

快照测试的思想:先执行一次测试,把输出结果记录到 .snap 文件,以后每次测试都会把输出结果和 .snap 文件做对比。

快照失败有两种可能:

  1. 业务代码有问题,要排查 Bug
  2. 新增功能改变了原有结构,要用 npx jest --updateSnapshot 更新当前快照(假错误)

现实中更多的情况是既在重构又要加新需求,如果开发者滥用快照测试,并生成很多大快照, 那么最终的结果是没有人再相信快照测试。一遇到快照测试不通过,都不愿意探究失败的原因,而是选择更新快照来 “糊弄一下”。

要避免这样的情况,需要做好两点:

  • 生成小快照。 只取重要的部分来生成快照,必须保证快照是能让你看懂的
  • 合理使用快照

前端单元测试策略与原则

💪 “你的测试与你的软件使用方式越相似,它们就越能给你带来信心。”

模块依赖和调用时的方法,都应该像软件模块真正被使用的时候一样。

思考:是否可以让测试来驱动开发?
先写测试,再写业务代码,当所有测试用例都通过后,你的业务代码也就实现完成了。
在这里插入图片描述

🔴:先写一个期望测试,得到“失败”
✅:开发代码,使测试通过
🔵:进行重构,使其可维护性更高

适用场景:

  1. 纯函数。不管逻辑是否简单,我们都很容易想到输入与输出,那么我们可以先写测试用例,覆盖90%的场景。
  2. UI交互。Mock需要的HTTP请求,用测试模拟用户操作,再去实现业务逻辑。
  3. 修BUG。用一个case来模拟复现问题,进行修复。

其实我们平常打的log,不仅log出来后需要删掉,而且最最重要的是只能测试一两种case,我们还需要手动刷新页面,进行肉眼找茬。从执行到肉眼看结果,这就是一种手动测试。

// 用测试用例来描述这个需求:
import objToSearchStr from 'utils/objToSearchStr'
describe('objToSearchStr', () => {test('可以将对象转化成查询参数字符串', () => {expect(objToSearchStr({ a: '1', b: '2' })).toEqual('a=1&b=2')})
})// 边看业务输入输出边实现代码逻辑:
const objToSearchStr = (obj: Record<string, string | number>) => {// ['a=1', 'b=2']const strPairs: string[] = []Object.entries(obj).forEach((keyValue) => {const [key, value] = keyValue // [a, 1]const pair = key + '=' + String(value) // a=1strPairs.push(pair)}, [])// a=1&b=2return strPairs.join('&')
}
export default objToSearchStr

你的代码的易测性也就代表着代码的可维护性。
反思:在保证单元测试独立性的前提下,什么样的模块才是符合【职责单一】原则的?

React 测试库
官网:React Testing Library
下载:npm install --save-dev @testing-library/react
文档:引入 React(纯配置)

⭕ 你可能会遇到的问题

  1. 使用 jest.mock() 时,文件路径找不到
    在这里插入图片描述

✅ 解决:在 jest.config.js 文件中配置路径中配置模块路径的别名:

{"jest": {"moduleNameMapper": {"^@/(.*)$": "<rootDir>/src/$1"}}
}
  1. 在使用 jest.spyOn() 时报错:Property xxx does not exist in the provided object
    原因:jest.spyOn() 用于监视对象的属性或方法,但在示例中 multiply 并不是一个对象,而是一个直接导出的函数。
    在这里插入图片描述

✅ 解决:将 multiply 放置在一个对象上,然后导出这个对象

// 错误代码
export const multiply = (a, b) => {return a + b
}// 改为:
export const math = {multiply: (a, b) => {return a + b},
}
  1. 测试异步函数时报错 regeneratorRuntime is not defined
    原因:这是因为 @babel/preset-env 不支持 async await 导致的。
    在这里插入图片描述

✅ 解决:要对 babel 配置进行增强,可以安装 @babel/plugin-transform-runtime 这个插件解决

// 安装依赖
npm install --save-dev @babel/plugin-transform-runtime// .babelrc.js 配置
"plugins": ["@babel/plugin-transform-runtime"]

Demo

// searchName.ts
import { getNames } from "../common/service";export const searchNames = (keyword) => {const results = getNames().filter((item) => item.includes(keyword));return results.length > 3 ? results.slice(0, 3) : results;
};export const sum = (a, b) => a + bexport const afterTime = (callback) => {console.log('准备计时')setTimeout(() => {console.log('时间到了')callback && callback()}, 2000)
}
// searchName.test.js
import { searchNames, sum, afterTime } from './searchName'
import { getNames } from '../common/service'jest.mock('../common/service', () => ({getNames: jest.fn()
}))test('sum', () => {expect(sum(1, 3)).toMatchInlineSnapshot(`4`)
})// mockImplementation:接受应该用作模拟实现的函数,当调用模拟时,实现也会被执行test('should return search empty result', () => {const keyword = 'Rose'getNames.mockImplementation(() => ['Jack'])const result = searchNames(keyword)expect(result).toEqual([])
})test('find target result', () => {const keyword = 'Jack'getNames.mockImplementation(() => ['Jack', 'Rose'])const result = searchNames(keyword)expect(result).toEqual(['Jack'])
})test('not return more than 3 result', () => {const keyword = 'Jack'getNames.mockImplementation(() => ['Jack1', 'Jack2', 'Jack3', 'Jack4'])const result = searchNames(keyword)expect(result).toHaveLength(3)
})test('should handle null or undefined as input', () => {expect(searchNames(null)).toEqual([])expect(searchNames(undefined)).toEqual([])
})describe('afterTime', () => {test('wait time', (callback) => {afterTime(() => {callback()expect(undefined)})})beforeAll(() => {// mock定时器jest.useFakeTimers()})test('fast', () => {// 监听setTimeoutjest.spyOn(global, 'setTimeout')// mock一个函数const callback = jest.fn()afterTime(callback)// 断言这个函数没有被调用expect(callback).not.toHaveBeenCalled()// 快进时间jest.runAllTimers()// 断言这个函数被调用了expect(callback).toHaveBeenCalled()expect(setTimeout).toHaveBeenCalledTimes(1)})
})

相关文章:

单元测试工具JEST入门——纯函数的测试

单元测试工具JEST入门——纯函数的测试 什么是测试❓&#x1f649; 我只是开发而已&#xff1f;常见单元测试工具 &#x1f527;jest的使用&#x1f440; 首先你得知道一个简单的例子&#x1f330;&#x1f628; Oops&#xff01;出现了一些问题&#x1f44f; 高效的持续监听&a…...

Elasticsearch Windows版安装配置

Elasticsearch简介 Elasticsearch是一个开源的搜索文献的引擎&#xff0c;大概含义就是你通过Rest请求告诉它关键字&#xff0c;他给你返回对应的内容&#xff0c;就这么简单。 Elasticsearch封装了Lucene&#xff0c;Lucene是apache软件基金会一个开放源代码的全文检索引擎工…...

安装 vant-ui 实现底部导航栏 Tabbar

本例子使用vue3 介绍 vant-ui 地址&#xff1a;介绍 - Vant 4 (vant-ui.github.io) Vant 是一个轻量、可定制的移动端组件库 安装 通过 npm 安装&#xff1a; # Vue 3 项目&#xff0c;安装最新版 Vant npm i vant # Vue 2 项目&#xff0c;安装 Vant 2 npm i vantlatest-v…...

GitHub国内打不开(解决办法有效)

最近国内访问github.com经常打不开&#xff0c;无法访问。 github网站打不开的解决方法 1.打开网站http://tool.chinaz.com/dns/ &#xff0c;在A类型的查询中输入 github.com&#xff0c;找出最快的IP地址。 2.修改hosts文件。 在hosts文件中添加&#xff1a; # localhost n…...

Unity之第一人称角色控制

目录 第一人称角色控制 &#x1f634;1、准备工作 &#x1f4fa;2、鼠标控制摄像机视角 &#x1f3ae;3、角色控制 &#x1f603;4.杂谈 第一人称角色控制 专栏Unity之动画和角色控制-CSDN博客的这一篇也有讲到角色控制器&#xff0c;是第三人称视角的&#xff0c;以小编…...

23种设计模式-结构型模式

1.代理模式 在软件开发中,由于一些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称为"代理"的第三者来实现间接访问.该方案对应的设计模式被称为代理模式. 代理模式(Proxy Design Pattern ) 原始定义是&#xff1a;让你能够提供对象的替代品或其占位符。…...

python -- 流程控制

1、if控制语句&#xff1a;语法格式&#xff1a; age 20 if age > 18:print("我不是小孩子") elif age < 18:print("你永远都是小孩子") else:print("你永远都是小孩子") 2、while循环语句&#xff1a;语法格式&#xff1a; age1 30 …...

Centos 7.9 在线安装 VirtualBox 7.0

1 访问 Linux_Downloads – Oracle VM VirtualBox 2 点击 ​the Oracle Linux repo file 复制 内容到 /etc/yum.repos.d/. 3 在 /etc/yum.repos.d/ 目录下新建 virtualbox.repo&#xff0c;复制内容到 virtualbox.repo 并 :wq 保存。 [rootlocalhost centos]# cd /etc/yum.rep…...

mysql之基本查询

基本查询 一、SELECT 查询语句 一、SELECT 查询语句 查询所有列 1 SELECT *FORM emp;查询指定字段 SELECT empno,ename,job FROM emp;给字段取别名 SELECT empno 员工编号 FROM emp; SELECT empno 员工编号,ename 姓名,job 岗位 FROM emp; SELECT empno AS 员工编号,ename …...

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之DataPanel组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之DataPanel组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、DataPanel组件 数据面板组件&#xff0c;用于将多个数据占比情况使用占比图进…...

qt5-入门

参考&#xff1a; qt学习指南 Qt5和Qt6的区别-CSDN博客 Qt 学习之路_w3cschool Qt教程&#xff0c;Qt5编程入门教程&#xff08;非常详细&#xff09; 本地环境&#xff1a; win10专业版&#xff0c;64位 技术选择 Qt5力推QML界面编程。QML类似HTML&#xff0c;可以借助CSS进…...

【重磅发布】已开放!模型师入驻、转格式再升级、3D展示框架全新玩法…

1月23日&#xff0c;老子云正式发布全新版本。此次新版本包含多板块功能上线和升级&#xff0c;为用户带来了含模型师入驻、三维格式在线转换升级、模型免费增值权益开放、全新3D展示框架等一系列精彩内容&#xff01; 1月23日&#xff0c;老子云正式发布全新版本。此次新版本…...

Qt 基础之QDataTime

Qt 基础之QDataTime 引言一、获取(设定)日期和时间二、时间戳三、时间计算 (重载运算符) 引言 QDataTime是Qt框架中用于处理日期和时间的类。它提供了操作和格式化日期、时间和日期时间组合的功能。QDataTime可以用于存储和检索日期和时间、比较日期和时间、对日期和时间执行算…...

美国将限制中国,使用Azure、AWS等云,训练AI大模型

1月29日&#xff0c;美国商务部在Federal Register&#xff08;联邦公报&#xff09;正式公布了&#xff0c;《采取额外措施应对与重大恶意网络行为相关的国家紧急状态》提案。 该提案明确要求美国IaaS&#xff08;云服务&#xff09;厂商在提供云服务时&#xff0c;要验证外国…...

智能指针|巨巨巨详细

智能指针 shared_ptrshared_ptr的基本用法使用shared_ptr要注意的问题 unique_ptr独占的智能指针weak_ptr弱引用的智能指针weak_ptr的基本用法weak_ptr返回this指针weak_ptr解决循环引用问题 weak_ptr使用注意事项 智能指针 C程序设计中使用堆内存是非常频繁的操作&#xff0…...

硬件知识(2) 手机的传感器-sensor

#灵感# 看看小米在干啥 手机型号&#xff1a;Redmi Note 13 Pro&#xff0c;解读一下它宣传的手机卖点。 目录 宣传1&#xff1a;1/1.4" 大底&#xff0c;f/1.65 大光圈&#xff0c; 宣传2&#xff1a;支持 2 亿像素超清直出&#xff0c;分辨率高达 16320 x 12240 宣…...

Kotlin快速入门系列9

Kotlin对象表达式和对象声明 对象表达式 有时&#xff0c;我们想要创建一个对当前类有些许修改的对象同时又不想重新声明一个子类。如果是Java&#xff0c;可以用匿名内部类的概念来解决这个问题。kotlin的对象表达式和对象声明就是为了实现这一点(创建一个对某个类做了轻微改…...

nginx+nginx-rtmp-module+ffmpeg进行局域网推流rtmp\m3u8

局域网推流的简单方式 这里以ubuntu为例 一、先下载安装包 nginx、nginx-rtmp-module&#xff0c;再一起安装 # 下载nginx # 这里我安装的是 nginx-1.10.3 版本 cd /usr/software wget http://nginx.org/download/nginx-1.25.0.tar.gz tar -zxvf nginx-1.25.0.tar.gz# 下载ng…...

PMP备考笔记:模拟考试知识点总结

1. 答题思路&#xff1a;优先看问题&#xff0c;可节省时间。 2. 考试就按照考试的套路来做&#xff0c;不要过多考虑。 开发团队只专注当前冲刺目标&#xff0c;产品负责人对PB排优先级。 收集需求工具-原型法&#xff1a;能够让用户提前体验&#xff0c;减少返工的风险。 …...

docker程序镜像的安装

目录 一、流程 二、总结 一、 流程 对文中脚本测试前提默认系统已安装docker docker程序部署命令脚本 加载已打包的docker程序 docker load < sto...p.tar创建网络 名称为c…m子网subnet 172.27.16.1/24网关gateway 172.27.16.254-d 指定网络驱动程序 docker network cre…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;社区养老保险系统小程序被用户普遍使用&#xff0c;为方…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

springboot整合VUE之在线教育管理系统简介

可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生&#xff0c;小白用户&#xff0c;想学习知识的 有点基础&#xff0c;想要通过项…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...

解读《网络安全法》最新修订,把握网络安全新趋势

《网络安全法》自2017年施行以来&#xff0c;在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂&#xff0c;网络攻击、数据泄露等事件频发&#xff0c;现行法律已难以完全适应新的风险挑战。 2025年3月28日&#xff0c;国家网信办会同相关部门起草了《网络安全…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)

引言 工欲善其事&#xff0c;必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后&#xff0c;我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集&#xff0c;就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...