当前位置: 首页 > 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…...

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

Java 8 Stream API 入门到实践详解

一、告别 for 循环&#xff01; 传统痛点&#xff1a; Java 8 之前&#xff0c;集合操作离不开冗长的 for 循环和匿名类。例如&#xff0c;过滤列表中的偶数&#xff1a; List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

大模型多显卡多服务器并行计算方法与实践指南

一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

如何在最短时间内提升打ctf(web)的水平?

刚刚刷完2遍 bugku 的 web 题&#xff0c;前来答题。 每个人对刷题理解是不同&#xff0c;有的人是看了writeup就等于刷了&#xff0c;有的人是收藏了writeup就等于刷了&#xff0c;有的人是跟着writeup做了一遍就等于刷了&#xff0c;还有的人是独立思考做了一遍就等于刷了。…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

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

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