前端工程化之自动化测试
自动化测试
- 自动化测试
- 为什么需要测试?
- 什么时候需要考虑测试
- 测试类型
- 前端测试框架
- 单元测试
- Jest 重点掌握
- 项目示例
- package.json
- src/utils/math.ts
- src/utils/math.test.ts
- 进行测试
- jest.config.js
- 覆盖率
- 直观看覆盖率
- coverage/lcov-report/index.html
- src/main.test.ts
- tsconfig.json
- 生命周期逻辑
- src/utils/math.test.ts
- src/utils/math2.test.ts
- 快照测试
- src/hooks/useBoolean.ts
- src/components/Button/index.tsx
- src/components/Button/Button.test.tsx
- coverage/lcov-report/index.html
- Vitest 重点掌握
- 项目示例
- 文件内容
- package.json
- vite.config.js
- plugins/myPlugin.js
- src/components/hello/index.vue
- src/components/hello/\_\_test\_\_/index.spec.js
- test/basic.test.js
- 启动测试
- src/components/hello/\_\_test\_\_/index.spec.js
- 按 u 更新快照:
- test-ui
- E2E测试 —— 端到端测试
- puppeteer 框架处理
- 安装
- 项目示例
- package.json
- jest.config.js
- .babelrc
- .babelrc
- 前端
- public/login.html
- public/index.html
- 后端
- server/index.js
- puppeteer/index.js
- puppeteer/screen.js
- 启动服务
提示
:自动化测试:写基础包的一定要进行自动化测试,为了让使用者使用的更放心,使用开源包也是看是否有自动化测试,还要看测试覆盖范围是否是比较齐全的,小厂的话不太关注,中大厂的话使用第三方的包是非常关注自动化测试的
自动化测试
为什么需要测试?
工作当中基本上是写业务代码,提交后将我们的功能提交给测试进行验证,为了保障整体的稳定性
- 及时发现问题
- 拆分模块的话,需要写单测
- 提高代码质量
什么时候需要考虑测试
- 公共库一类项目,一定要前端测试
- 中长期项目的迭代,建议需要测试,单元测试,E2E测试
- 重构场景,相关代码有了测试后,接手项目时候,可能并不了解业务逻辑,业务架构,重构项目会面临大的风险,但是有了单元测试,可以看到之前测试
测试类型
单元测试 unit test
,小的功能
模块进行测试
E2E测试
,端到端的测试,测试 UI+接口 相关内容,会将一些固定的流程,变成自动化任务,之后跑一下自动化任务,通过这种E2E测试在模拟设备上做一些模拟的交互,将流程固化,人工只需要看每个步骤的效果,截图等集成测试,模块测试
UI 测试
整体页面出发,看页面布局,看页面是否有空窗等,在布局维度,看是否与预期相符 => 使用场景:灰度发布,UI布局的验证,稳定性 => 是否白屏
前端测试框架
- 测试框架:Jest、vitest(vite)、@vue/test-utils、mocha(少用)
- 断言库:Assert
- 工具:无头浏览器 puppeteer(node环境下的浏览器,使用chrome内核),可以读取对应文件,以及对文件进行爬取,获取当前文件结构,还有截屏功能
单元测试
通过确定的测试用例
来编写测试代码
,保证测试用例通过,以及测试覆盖率范围
判断测试用例的结果是true还是false,表明测试通过与否
例子:
test.js:
// test.js
const chalk = require('chalk')
// 测试原理部分的内容
// 一般测试框架(jest)需要做的事情// 描述测试场景
function describe(desc, fn) {console.log(chalk.green(desc))fn()
}// 单元用例测试描述
function it(desc, fn) {console.log(chalk.yellow(desc))fn()
}// 作比对,看是 true、还是 false,这个库其实在测试领域有一个专业术语叫:断言库
function expect(result) {return {toBe(actual) {if (result != actual) {console.log(chalk.red('FAIL', result, actual))throw new Error(`预期值和实际值不相等,预期${actual},实际${result}`)}console.log(chalk.green('PASS', result, actual))},toEqual(actual) {if (result !== actual) {throw new Error(`预期值和实际值不相等,预期${actual},实际${result}`)}},}
}module.exports = {describe,it,expect,
}
创建测试文件:
__index__.test.js
// 创建测试文件
// __index__.test.jsconst { describe, it, expect } = require('./test')// 定义 sum 函数
function sum(a, b) {return a + b
}// 测试用例
describe('用来测试相关内容', () => {it('测试 sum 函数', () => {expect(sum(1, 1)).toBe(2)})it('测试 sum 函数其他的场景', () => {expect(sum(1, 2)).toBe(4)})
})
Jest 重点掌握
项目示例
package.json
{"name": "react-test-demo","version": "1.0.0","description": "","main": "index.js","scripts": {"test": "jest"},"keywords": [],"license": "ISC","dependencies": {"react": "18.2.0","react-dom": "18.2.0"},"devDependencies": {"@types/react": "18.2.73","@babel/core": "7.23.2","@babel/preset-env": "7.23.2","@babel/preset-react": "7.22.15","@babel/preset-typescript": "7.23.2","babel-loader": "9.1.3","typescript": "5.2.2","webpack": "5.89.0","webpack-cli": "5.1.4","jest": "29.7.0","@jest/globals": "29.7.0","ts-jest": "29.1.2","react-test-renderer": "18.2.0"}
}
src/utils/math.ts
export const sum = (a: number, b: number) => a + b
export const subtract = (a: number, b: number) => a - b
export const multiply = (a: number, b: number) => a * b
export const divide = (a: number, b: number) => a / b
export const pow = (a: number, b: number) => a ** b
src/utils/math.test.ts
import { describe, expect, test } from '@jest/globals'describe('math test', () => {test('sum test', () => {expect(1 + 2).toBe(3)})
})
进行测试
npx ts-jest config:init
=> 生成 jest.config.js文件
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {preset: 'ts-jest',testEnvironment: 'node',
};
pnpm run test
jest.config.js
增加配置:
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {preset: 'ts-jest',testEnvironment: 'node',collectCoverage: true, //开启当前测试覆盖率collectCoverageFrom: ['src/**/*.{ts,tsx}'], //测试覆盖率的范围,tsx:需要进行快照测试
}
重新执行:
pnpm run test
=> 生成测试覆盖率的结果 以及 coverage 目录文件
覆盖率
- stmts 语句覆盖率
- branch 分支覆盖率
- funcs 函数覆盖率
- lines 行覆盖率
直观看覆盖率
coverage/lcov-report/index.html
页面从浏览器中打开
math.ts 点进去看:
由于main.ts中没有一个被引用,因此为 Statements 语句 为 0
src/main.test.ts
内容改了后:
import { describe, expect, test } from '@jest/globals'
import { sum } from './math'
describe('math test', () => {test('sum test', () => {expect(sum(1,2)).toBe(3)})
})
pnpm run test
执行结果:
点进去 main.ts,发现只有 sum 没标红
tsconfig.json
{"compilerOptions": {"jsx": "react", //react编译jsx"esModuleInterop": true //esm},"include": ["./src/**/*"], //包含文件"exclude": ["node_modules"] //不包含文件
}
生命周期逻辑
- beforeAll 所有用例执行前调用
- beforeEach 每个用例执行前
- afterAll 所有测试完毕
- afterEach 每个case end
断言钩子:
- toBe 严格对比 => 内存空间一致
- toEqual 值对比 => 对象递归遍历,对比值
const x = { a: { b: 3 } }
const y = { a: { b: 3 } }console.log(expect(x).toBe(y)) // fail
console.log(expect(x).toEqual(y)) // pass
src/utils/math.test.ts
import {afterAll,beforeAll,beforeEach,describe,expect,test,afterEach,
} from '@jest/globals'
import { divide, multiply, subtract, sum } from './math'describe('math test', () => {beforeAll(() => {console.log('beforeAll')})beforeEach(() => {console.log('beforeEach')})test('sum test', () => {expect(sum(1, 2)).toBe(3)})test('subtract test', () => {expect(subtract(2, 1)).toBe(1)})test('multiply test', () => {expect(multiply(2, 3)).toBe(6)})test('divide test', () => {expect(divide(6, 3)).toBe(2)})afterEach(() => {console.log('afterEach')})afterAll(() => {console.log('afterAll')})
})
pnpm run test
src/utils/math2.test.ts
借助AI工具生成测试用例:
import { expect, test } from '@jest/globals'
import { pow } from '../utils/math'test('Should return the correct result for positive integer powers', () => {expect(pow(2, 3)).toBe(8)expect(pow(3, 2)).toBe(9)expect(pow(5, 1)).toBe(5)
})
test('Should return 1 when the base and exponent are both 0', () => {expect(pow(0, 0)).toBe(1)
})
test('Should return NaN when the exponent is NaN', () => {expect(pow(2, NaN)).toBe(NaN)
})
快照测试
想要确保UI不会有意外变化的时候,快照是一个非常有用的工具
典型的是,在渲染UI组件后,会保存一个快照文件,主要是为了跟下次执行当前快照时候进行一个比对,看两者比对的结果,当前的case和快照的能否匹配上,匹配不上就是一个异常,当然也能更改快照测试的版本
src/hooks/useBoolean.ts
import { useState } from 'react'export const useBoolean = (initialValue: boolean) => {const [bol, setBol] = useState(initialValue)const toggle = () => setBol(!bol)return [bol, toggle] as const
}
src/components/Button/index.tsx
import React from 'react'
import { useBoolean } from '../../hooks/useBoolean'const bottonTypes = ['primary', 'secondary', 'tertiary'] as consttype ButtonType = (typeof bottonTypes)[number]interface ButtonProps {type: ButtonTypechildren: React.ReactNode
}export const Button: React.FC<ButtonProps> = ({type = 'primary',children,
}) => {const [isDisabled, toggle] = useBoolean(false)return (<buttondisabled={isDisabled}onClick={toggle}className={`button button--${type}`}>{children}</button>)
}
src/components/Button/Button.test.tsx
import renderer from 'react-test-renderer' //渲染当前组件
import { Button } from './index'
import React from 'react'
import { describe, it, expect } from '@jest/globals'describe('Button', () => {it('renders correctly', () => {const tree = renderer.create(<Button type="primary">Hello4</Button>).toJSON()expect(tree).toMatchSnapshot()})
})
pnpm run test
生成_snapshots_文件:
将 hello4 改为 hello 会报错:
import renderer from 'react-test-renderer'
import { Button } from './index'
import React from 'react'
import { describe, it, expect } from '@jest/globals'describe('Button', () => {it('renders correctly', () => {const tree = renderer.create(<Button type="primary">Hello</Button>).toJSON()expect(tree).toMatchSnapshot()})
})
更新快照:
npx jest --updateSnapshot
重新生成一个快照:
coverage/lcov-report/index.html
点击 utils:
点击 hooks:
点击 components/Button:
Vitest 重点掌握
vitest
一个原生支持 Vite 的测试框架
项目示例
文件内容
package.json
{"name": "my-vue3-app","private": true,"version": "1.0.20","type": "module","scripts": {"dev": "vite --debug","build": "vite build","preview": "vite preview",// 追加一些脚本"test": "vitest", //使用Vitest进行测试"test:coverage": "vitest run --coverage", //开启测试覆盖率"test:ui": "vitest --ui" //使用UI的方式查看测试覆盖率的情况},"dependencies": {"@vitest/ui": "^3.0.9", //vitest --ui命令需要的依赖"vue": "^3.4.19"},"devDependencies": {"@vitejs/plugin-vue": "^5.0.4","@vitest/coverage-istanbul": "^3.0.8","@vitest/coverage-v8": "3.0.8", //跑测试用例"@vue/test-utils": "^2.4.6", //渲染当前组件的方法和工具类"happy-dom": "^17.4.2", //happy-dom和jsdom可以二选一,获取当前dom数据和结构,触发当前dom节点"jsdom": "^26.0.0","vite": "^5.1.4","vitest": "^3.0.8" //还需要安装vitest依赖}
}
vite.config.js
vitest 可以复用vite配置,因此在vite.config.js中需要添加test相关的配置项
/// <reference types="vitest" />import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import myVitePlugin from './plugins/myPlugin'// https://vitejs.dev/config/
export default defineConfig({plugins: [vue(), myVitePlugin()],// test相关的配置项test: {environment: 'jsdom', //jsdom/happy-dom模拟dom// 覆盖率coverage: {reporter: ['text', 'json', 'html'],// 设置覆盖文件夹reportsDirectory: './coverage',// 检查每个文件的阈值perFile: true,// 设置代码覆盖率阈值lines: 75,functions: 75,branches: 75,statements: 75,},},
})
plugins/myPlugin.js
import path from 'path'
import fs from 'fs'
// 控制台打印当前工程版本号
export default function myVitePlugin() {let version, configreturn {name: 'my-vite-plugin',configResolved(resolvedConfig) {config = resolvedConfigconst pkgPath = path.resolve(config.root, 'package.json')const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))version = pkg.version},buildStart() {console.log('当前工程版本号:1.0.0')},transform(code, id) {if (id.endsWith('main.js')) {const info = `console.log('当前工程版本号:${version}')`return `${code}\n${info}\n`}},}
}
src/components/hello/index.vue
<template><div>{{ count }} x {{ times }} = {{ result }}</div><button @click="times += 1">x1</button>
</template><script setup lang="ts">
import { computed, ref } from 'vue'const props = defineProps<{ count: number }>()const times = ref(2)
const result = computed(() => props.count * times.value)defineExpose(props)
</script>
src/components/hello/__test__/index.spec.js
// Hello/__test__/index.spec.ts
import { mount } from '@vue/test-utils'
import { describe, expect, test } from 'vitest'import Hello from '../index.vue'
describe('Hello', () => {test('挂载组件', async () => {// 断言当前组件存在expect(Hello).toBeTruthy()// 挂载当前组件内容const wrapper = mount(Hello, {props: {count: 4,},})// 获取组件的button触发点击事件,模拟用户点击await wrapper.get('button').trigger('click')// 点击后,组件的内容发生变化,断言的值也会发生变化 expect(wrapper.text()).toContain('4 x 3 = 12')// 再次点击await wrapper.get('button').trigger('click')// 断言组件再次内容变化expect(wrapper.text()).toContain('4 x 4 = 16')})// 对当前组件进行快照测试test('组件渲染快照', () => {const wrapper = mount(Hello, {props: {count: 5,},})expect(wrapper.html()).toMatchSnapshot()})
})
test/basic.test.js
import { assert, describe, expect, it } from 'vitest'describe('suite name', () => {it('foo', () => {assert.equal(Math.sqrt(4), 2)})it('bar', () => {expect(1 + 1).eq(2)})it('snapshot', () => {expect({ foo: 'bar' }).toMatchSnapshot()})
})
启动测试
pnpm run test
生成快照:
src/components/hello/__test__/index.spec.js
修改组件快照count的值
// Hello/__test__/index.spec.ts
import { mount } from '@vue/test-utils'
import { describe, expect, test } from 'vitest'import Hello from '../index.vue'
describe('Hello', () => {test('挂载组件', async () => {// 断言当前组件存在expect(Hello).toBeTruthy()// 挂载当前组件内容const wrapper = mount(Hello, {props: {count: 4,},})// 获取组件的button触发点击事件,模拟用户点击await wrapper.get('button').trigger('click')// 点击后,组件的内容发生变化,断言的值也会发生变化 expect(wrapper.text()).toContain('4 x 3 = 12')// 再次点击await wrapper.get('button').trigger('click')// 断言组件再次内容变化expect(wrapper.text()).toContain('4 x 4 = 16')})// 对当前组件进行快照测试test('组件渲染快照', () => {const wrapper = mount(Hello, {props: {count: 6, //修改组件快照count的值},})expect(wrapper.html()).toMatchSnapshot()})
})
报错:
按 u 更新快照:
test-ui
pnpm run test:ui
浏览器打开页面
E2E测试 —— 端到端测试
puppeteer 框架处理
- 生成快照,图片/pdf 的形式进行存储
- 爬取页面内容
- 表单自动化提交
- UI测试
- 测试 chrome 扩展 (chrome内核就会让我们在node环境下去获取当前页面的内容,不会再去局限于浏览器环境下面)
- 页面性能数据
重要的点
- Browser 对应浏览器实例
- Page,tab页面
- ExecutionContext,js 的执行环境
- ElementHandle 对应 dom 元素节点
- jsHandler 对应dom中 js 对象
安装
pnpm install puppeteer
项目示例
package.json
{"name": "f2e-test","version": "1.0.0","description": "前端测试","main": "index.js","scripts": {"test": "jest --watchAll"},"keywords": [],"author": "","license": "ISC","devDependencies": {"@babel/core": "^7.20.5","@babel/preset-env": "^7.20.2","@babel/preset-typescript": "^7.18.6","@types/jest": "^29.2.4","body-parser": "^1.20.1","express": "^4.18.2","jest": "^29.3.1","puppeteer": "^19.4.1"}
}
jest.config.js
module.exports = {// 是否显示覆盖率报告collectCoverage: true,collectCoverageFrom: ['src/**/*'],coverageThreshold: {global: {statements: 90,functions: 90,branches: 90,},},
};
.babelrc
{"presets": ["@babel/preset-env", "@babel/preset-typescript"]
}
.babelrc
前端
public/login.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>login</title><style>div {text-align: center;}button {display: inline-block;line-height: 1;white-space: nowrap;cursor: pointer;text-align: center;box-sizing: border-box;outline: none;margin: 0;transition: 0.1s;font-weight: 500;padding: 12px 20px;font-size: 14px;border-radius: 4px;color: #fff;background-color: #409eff;border-color: #409eff;border: 0;}button:active {background: #3a8ee6;border-color: #3a8ee6;color: #fff;}input {display: block;margin: auto;margin-bottom: 10px;-webkit-appearance: none;background-color: #fff;background-image: none;border-radius: 4px;border: 1px solid #dcdfe6;box-sizing: border-box;color: #606266;font-size: inherit;height: 40px;line-height: 40px;outline: none;padding: 0 15px;transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);}</style></head><body><div><input type="text" placeholder="请输入账号" class="account" /><input type="password" placeholder="请输入密码" class="password" /><button id="btn-login">登录</button></div><script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.0/axios.min.js"></script><script>document.querySelector('button').onclick = () => {axios.post('/login', {account: document.querySelector('.account').value,password: document.querySelector('.password').value,}).then((res) => {if (res.data.code == 0) {location.href = '/index.html';} else {alert(res.data.msg);}});};</script></body>
</html>
public/index.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>index</title></head><body>Hello World!</body>
</html>
后端
server/index.js
const bodyParser = require('body-parser');
const express = require('express');
const app = express();
const port = 8080;app.use(express.static('public'));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());app.post('/login', (req, res) => {const { account, password } = req.body;// 由于没有注册功能,所以假定账号密码都为 adminif (account == 'admin' && password == 'admin') {res.send({msg: '登录成功',code: 0,});} else {res.send({msg: '登录失败,请输入正确的账号密码',code: 1,});}
});app.listen(port, () => {console.log(`Example app listening at http://localhost:${port}`);
});
puppeteer/index.js
const puppeteer = require('puppeteer');(async () => {// 无头浏览器,创建browser实例const browser = await puppeteer.launch({headless: false,});// 创建一个页面const page = await browser.newPage();// 访问页面await page.goto("http://localhost:8080/login.html");// 3. 初始页面截图await page.screenshot({path: `screenshot_${new Date().getTime()}.png`,});// 4. 获取dom元素 输入内容await page.type(".account", "admin");await page.type(".password", "admin");setTimeout(async () => {// 5.模拟点击操作const btnConfirm = await page.$("#btn-login");await Promise.all([btnConfirm.click(), page.waitForNavigation()]);// 6.最后截屏await page.screenshot({path: `screenshot_${new Date().getTime()}.png`,});}, 5000);// elementhandle//// browser.close()
})()
puppeteer/screen.js
const puppeteer = require('puppeteer');
const sleep = (time) => {new Promise((resolve, reject) => {setTimeout(resolve, time);});
};(async () => {const browser = await puppeteer.launch({headless: false,});const page = await browser.newPage();await page.goto('https://baidu.com');// await page.screenshot({// path: `screenshot_${new Date().getTime()}.png`,// });// 理解两个环境// node// page dom environment// elementhandle jshandleconst input = await page.$('#form');await sleep(5000);await input.screenshot({path: `screenshot_${new Date().getTime()}.png`,});browser.close();
})();
启动服务
pnpm i
node server/index.js
node .\puppeteer\index.js
相关文章:
前端工程化之自动化测试
自动化测试 自动化测试为什么需要测试?什么时候需要考虑测试测试类型前端测试框架单元测试Jest 重点掌握项目示例package.jsonsrc/utils/math.tssrc/utils/math.test.ts进行测试jest.config.js覆盖率直观看覆盖率coverage/lcov-report/index.html src/main.test.tst…...
CANFD技术在新能源汽车通信网络中的应用与可靠性分析
一、引言 新能源汽车产业正处于快速发展阶段,其电子系统复杂度不断攀升,涵盖众多传感器、控制器与执行器。高效通信网络成为确保新能源汽车安全运行与智能功能实现的核心要素。传统CAN总线因带宽限制,难以满足高级驾驶辅助系统(A…...
【机器学习】朴素贝叶斯算法:原理剖析与实战应用
引言 朴素贝叶斯算法就像是一位善于从经验中学习的侦探,根据已有的线索来推断未知事件的概率。这是一种基于概率论的分类算法,以贝叶斯定理为基础,却做了一个"朴素"的假设:认为所有特征彼此独立。虽然这个假设在现实中…...
【更新完毕】2025妈妈杯C题 mathercup数学建模挑战赛C题数学建模思路代码文章教学:音频文件的高质量读写与去噪优化
完整内容请看文章最下面的推广群 我将先给出文章、代码、结果的完整展示, 再给出四个问题详细的模型 面向音频质量优化与存储效率提升的自适应编码与去噪模型研究 摘 要 随着数字媒体技术的迅速发展,音频处理技术在信息时代的应用愈加广泛,特别是在存储…...
UI键盘操作
1、Selenium中send_keys除了可以模拟键盘输入之外,还有些时候需要操作键盘上的按键,甚至是组合键,比如CTRLA,CTRLC等, 所以我们需要代码操作键盘。使用的是send_keys里的Keys的类。 from selenium.webdriver.common.keys import …...
【正则表达式】正则表达式使用总结
正则表达式除了匹配普通字符外,还可以匹配特殊字符,这些特殊字符被称为“元字符”。 特殊字符(元字符) 限定符:用于指定正则表达式中某个组件的出现次数。常见的限定符包括: *:0次或多次 +:1次或多次 ?:0次或1次 {n}:恰好n次…...
Qt编写推流程序/支持webrtc265/从此不用再转码/打开新世界的大门
一、前言 在推流领域,尤其是监控行业,现在主流设备基本上都是265格式的视频流,想要在网页上直接显示监控流,之前的方案是,要么转成hls,要么魔改支持265格式的flv,要么265转成264,如…...
Spring Boot 中基于 Reactor 的服务器端事件(SSE)推送机制实践
Spring Boot 3.0 中基于 Reactor 的服务器端事件(SSE)推送机制实践 在现代 Web 应用开发中,实时数据交互越来越成为刚需,从股票行情的实时更新到社交平台的消息即时推送,服务器端事件(Server-Sent Events,简称 SSE)作为一种高效的单向数据传输技术,正发挥着重要作用。…...
CRC实战宝典:从原理到代码,全面攻克循环冗余校验
CRC实战宝典:从原理到代码,全面攻克循环冗余校验 github开源:CRC软硬件协同测试项目 CRC 简介 CRC(循环冗余校验)是一种强大的错误检测技术,广泛应用于数字网络和存储系统。它是确保数据完整性的重要方法…...
【愚公系列】《Python网络爬虫从入门到精通》056-Scrapy_Redis分布式爬虫(Scrapy-Redis 模块)
🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! …...
ZLMediaKit 和 SRS的区别,哪个更好用?
ZLMediaKit 和 SRS(Simple RTMP Server)是两个主流的开源流媒体服务器框架,各自在功能、性能、适用场景等方面存在显著差异。以下是两者的对比分析及选择建议: 一、核心差异对比 协议支持 ZLMediaKit:支持更广泛的流媒…...
【PyTorch】colab上跑VGG(深度学习)数据集是 CIFAR10
跑得结果是测试准确率10%,欠拟合。 import torch import torchvision.datasets from torch import nn from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter from torchvision import datasets, transformstransform tran…...
pytorch 51 GroundingDINO模型导出tensorrt并使用c++进行部署,53ms一张图
本专栏博客第49篇文章分享了将 GroundingDINO模型导出onnx并使用c++进行部署,并尝试将onnx模型转换为trt模型,fp16进行推理,可以发现推理速度提升了一倍。为此对GroundingDINO的trt推理进行调研,发现 在GroundingDINO-TensorRT-and-ONNX-Inference项目中分享了模型导出onnx…...
编程语言基础 - C++ 面试题
C++ 面试题 tags: c++ 文章目录 C++ 面试题关键字1. const2. static3. this 指针4. inline 内联函数5. volatile6. struct, class7. enum关键字 1. const 修饰变量:该变量不能被改变 修饰指针: 指针常量: 指针本身是常量 TYPE* const pContent;指向常量的指针:指针所指向…...
JVM笔记【一】java和Tomcat类加载机制
JVM笔记一java和Tomcat类加载机制 java和Tomcat类加载机制 Java类加载 * loadClass加载步骤类加载机制类加载器初始化过程双亲委派机制全盘负责委托机制类关系图自定义类加载器打破双亲委派机制 Tomcat类加载器 * 为了解决以上问题,tomcat是如何实现类加载机制的…...
Python----深度学习(全连接与链式求导法则)
一、机器学习和深度学习的区别 机器学习:利用计算机、概率论、统计学等知识,输入数据,让计算机学会新知 识。机器学习的过程,就是训练数据去优化目标函数。 深度学习:是一种特殊的机器学习,具有强大的能力和…...
基于SpringBoot的网上找律师管理系统
博主介绍:java高级开发,从事互联网行业六年,熟悉各种主流语言,精通java、python、php、爬虫、web开发,已经做了六年的毕业设计程序开发,开发过上千套毕业设计程序,没有什么华丽的语言࿰…...
《目标检测双雄:YOLO与Faster R-CNN,谁主沉浮?》
在计算机视觉的广阔天地里,目标检测技术宛如一颗璀璨的明星,照亮了无数应用场景。从安防监控中对行人与车辆的精准识别,到自动驾驶领域对道路障碍物的快速判断,再到工业生产里对产品缺陷的严格检测,目标检测无处不在&a…...
CUDA编程中影响性能的小细节总结
一、内存访问优化 合并内存访问:确保相邻线程访问连续内存地址(全局内存对齐访问)。优先使用共享内存(Shared Memory)减少全局内存访问。避免共享内存的Bank Conflict(例如,使用padding或调整访…...
C#学习第17天:序列化和反序列化
什么是序列化? 定义:序列化是指把对象转换为一种可以轻松存储或传输的格式,如JSON、XML或二进制格式。这个过程需要捕获对象的类型信息和数据内容。用途:使得对象可以持久化到文件、发送至网络、或存储在数据库中。 什么是反序列…...
kafka的零拷贝技术
在 Kafka 中,高性能数据传输依赖于操作系统提供的 零拷贝(Zero-Copy) 技术,主要包括 sendfile 和 mmap 两种实现方式。它们的核心目标是减少数据在用户态和内核态之间的拷贝次数,从而提升 I/O 效率。下面详细解析它们的…...
从 0~1 保姆级 详细版 PostgreSQL 数据库安装教程
PostgreSQL数据库安装 PostgreSQL官网 【PostgreSQL官网】 | 【PostgreSQL安装官网_Windows】 安装步骤 step1: 选择与电脑相对应的PostgreSQL版本进行下载。 step2: 双击打开刚才下载好的文件。 step3: 在弹出的setup窗口中点击 …...
MySQL中常用函数的分类及示例
概述 以下是 MySQL 中常用函数的分类及示例,涵盖字符串处理、数值计算、日期操作、条件判断等常见场景: 一、字符串函数 1. CONCAT(str1, str2, ...) 拼接字符串。 SELECT CONCAT(Hello, , World); -- 输出: Hello World2. SUBSTRING(str, start,…...
【论文阅读21】-PSOSVM-CNN-GRU-Attention-滑坡预测(2024-12)
这篇论文主要提出并验证了一种新型的混合智能模型(PSOSVM-CNN-GRU-Attention),用于准确预测滑坡的点位移,并构建可靠的位移预测区间。通过对Baishuihe滑坡和Shuping滑坡的案例分析,展示了该模型的出色性能。 [1] Zai D…...
Shiro-550 动调分析与密钥正确性判断
一、Shiro 简介 Apache Shiro是一个开源安全框架,用于构建 Java 应用程序,提供身份验证、授权、加密和会话管理等功能。 二、Shiro-550(CVE-2016-4437) 1、漏洞原理 Shiro 在用户登陆时提供可选项 RememberMe,若勾选…...
Codeforces Educational Round 177 Div. 2 【B题,C待补
B 二分 题意 样例 5 3 10 3 4 2 1 512 找最右边的L下标即可 思路 二分最靠右的L端点,R端点取最右端(n*k处),找到后,答案就是L的位置(pos),(因为如果pos满足,则pos左边的所有下标都满足 代码 const in…...
【Lua语言】Lua语言快速入门
初始Lua Lua是一种轻量小巧的脚本语言,他使用标准C语言编写并以源代码形式开放。这意味着Lua虚拟机可以很方便的嵌入别的程序中,从而为应用程序提供灵活的扩展和定制功能。同时,在目前脚本引擎中,Lua的运行速度占有绝对优势。 变…...
Matlab画海洋与大气变量的时间序列并带标记面的三维折线图--来源粉丝
Matlab画带标记面的三维折线图–来源粉丝 图片 目标图: 图片 复现: 图片 细节可在代码中更改: 数据构造 clear;clc;close all; % 数据构造 X1 1:8;Y1ones(length(X1),1); X2 X1;Y22*ones(length(X1),1); X3 X1;Y33*ones(length(X1),1); …...
NestJS——多环境配置方案(dotenv、config、@nestjs/config、joi配置校验)
个人简介 👀个人主页: 前端杂货铺 🙋♂️学习方向: 主攻前端方向,正逐渐往全干发展 📃个人状态: 研发工程师,现效力于中国工业软件事业 🚀人生格言: 积跬步…...
RAGFlow在Docker中运行Ollama直接运行于主机的基础URL的地址
基础Url http://host.docker.internal:11434...
python 库 下载 ,整合在一个小程序 UIUIUI
上图 import os import time import threading import requests import subprocess import importlib import tkinter as tk from tkinter import ttk, messagebox, scrolledtext from concurrent.futures import ThreadPoolExecutor, as_completed from urllib.parse import…...
【MySQL】数据库约束
个人主页:♡喜欢做梦 欢迎 👍点赞 ➕关注 ❤️收藏 💬评论 目录 ✨一、数据库的约束 🌟二、数据库约束的分类 🌍 1.非空约束(NOT NULL) 1.定义 2.格式 3.示例: 列的信息可…...
Firewalld防火墙
目录 Firewald 防火墙概述 Firewalld 简介 firewalld 与 iptables service的区别 Firewalld 网络区域 Firewalld 防火墙图形配置方法 服务选项 端口号 协议选项 源端口选项 伪装选项 端口转发 ICMP过滤器 防火墙的配置运行状态 运行时和永久有什么区别 Firewalld 防火墙 firewa…...
使用 TensorFlow 和 Keras 构建 U-Net
U-Net是图像分割领域中最为著名的架构之一。U-Net 因其形状而得名,它是一种全卷积架构,首先将图像收缩,然后将其扩展为输出结果。虽然这种收缩路径构建了一个学习特征的层次结构,但跳过连接有助于在扩展路径中将这些特征转换回相关…...
【网络篇】TCP vs UDP底层区别+网络编程概念
大家好呀 我是浪前 今天讲解的是网络篇的第三章:网络编程概念和TCP&UDP的区别 网络编程概念TCP和UDP的区别 跨主机通信:网络编程插座:网络编程的本质: 网络编程的重要概念:客户端和服务器: 客户端和服务器的交互模…...
如何保存服务器mysql数据库的数据到本地文件
打开mysql命令行如图1 图1 mysql命令行 修改文件保存路径。 在mysql安装目录下,找到my.ini文件,找到secure-file-priv变量配置的地方,修改对应的值,然后重启mysql,此时把文件放到指定路径,再执行导入导出…...
Flutter学习 滚动组件(2):ListView进阶使用
目录 前言:一、实现复杂的ListView列表:1.1 Item布局封装1.2 ListView的使用1.3 增加分割线 二、实现ListView下拉刷新:三、实现上拉加载更多:四、实现下拉刷新、上拉加载更多:五、ListView滚动方向和控制:…...
linux oracle 19c 静默安装
oracle数据库有个比较很抓瞎的事情,不同的版本搭建的大致流程是一样的,但是在实操细节上会有不同,比如操作的脚本位置和配置项等等,这些会变,所以需要时常积累不同版本的文档 这里有一点要说明,之所以使用…...
中间件--ClickHouse-11--部署示例(Linux宿主机部署,Docker容器部署)
一、Linux宿主机部署 1、环境准备 操作系统:推荐使用 CentOS 7/8 或 Ubuntu 18.04/20.04。硬件要求: 至少 2 核 CPU 和 4GB 内存。足够的磁盘空间(根据数据量评估)。CPU需支持SSE4.2指令集(可通过以下命令检查&#…...
AI调试工具有哪些?
一、深度学习框架专用调试工具 TensorBoard • 功能:实时监控训练指标(损失值、准确率)、可视化神经网络结构、分析参数分布和梯度信息 • 适用框架:TensorFlow、PyTorch(通过插件) • 特点:支持…...
Warcraft Logs [Classic] [WCL] BOSS ID query
Warcraft Logs [Classic] [WCL] BOSS ID query 所有副本BOSSID查询 https://wowpedia.fandom.com/wiki/DungeonEncounterID#Retail IDNameMapInstanceIDPatch227High Interrogator GerstahnBlackrock Depths230228Lord RoccorBlackrock Depths230229Houndmaster GrebmarBlackro…...
MySQL——事务
一、什么是事务? 事务(Transaction) 是数据库操作的最小逻辑单元,它由一组不可分割的SQL操作组成。事务的核心目标是确保多个操作要么全部成功,要么全部失败,从而维护数据的完整性。例如,银行转…...
spring Ai---向量知识库(一)
在一些垂直领域以及公司内部信息相关或者实时性相关的大模型应用,就无法直接使用chatGPT。 这个时候,向量知识库就进入了。 通过坐标向量最接近的即为匹配相关答案。 向量模型定义:将文档向量化,保证内容越相似的文本,…...
MACOS 上的 快捷指令怎么用,有哪些分享资源可以用
一、快捷指令的基本概念与历史 快捷指令(Shortcuts)是苹果生态中的自动化工具,最初以第三方应用Workflow(2014年推出)的形式出现,2017年被苹果收购后更名为Shortcuts,并深度集成到iOS、iPadOS和macOS系统中。从macOS Mojave(10.14)开始,快捷指令正式登陆Mac平台,并…...
最长子序列长度(LIS)--个数遍历的二分+贪心优化
B3637 最长上升子序列 - 洛谷 #include<bits/stdc.h> #include<string> using namespace std; #define N 100011 typedef long long ll; typedef pair<int,int> pii; int n; int g[N]; int dp[N]; int ma0; int main() { cin>>n; memset(g,0x3f,sizeo…...
RenderStage::runCameraSetUp
文章目录 RTTosg::Camera::_bufferAttachmentMapRenderStage::BufferComponent和RenderStage::_bufferAttachmentMapCamera::attach(BufferComponent buffer, GLenum internalFormat)Camera::attach(BufferComponent buffer, osg::Texture* texture.....Camera::attach(BufferC…...
突破速率瓶颈:毫米波技术如何推动 5G 网络迈向极限?
突破速率瓶颈:毫米波技术如何推动 5G 网络迈向极限? 引言 5G 网络的普及,已经让我们告别了“加载中”时代,实现了更快的数据传输、更低的延迟和更高的设备连接密度。而在 5G 技术的核心中,毫米波(mmWave&…...
前端面试真题集合(一)
一、Vue的响应式原理 Vue的响应式系统通过数据劫持和依赖追踪实现,核心流程如下: 数据劫持 • Vue 2.x:使用Object.defineProperty递归遍历数据对象,将属性转换为getter/setter,拦截属性的读取和修改操作。 • Vue 3.x:改用Proxy代理对象,支持动态属性添加和数组变化监听…...
聊聊Spring AI Alibaba的ElasticsearchDocumentReader
序 本文主要研究一下Spring AI Alibaba的ElasticsearchDocumentReader ElasticsearchDocumentReader community/document-readers/spring-ai-alibaba-starter-document-reader-elasticsearch/src/main/java/com/alibaba/cloud/ai/document/reader/es/ElasticsearchDocumentR…...
【网络技术_域名解析DNS】三、DNS 中间件实践应用与优化策略
一、DNS 中间件在典型行业的实践应用 1.1 金融行业:保障交易安全与服务稳定 金融行业对网络服务的安全性和稳定性要求极高,DNS 中间件在此领域发挥着不可替代的作用。以某银行线上支付系统为例,在 CentOS 7 环境下部署 DNS 中间件时&…...