记录使用vue-test-utils + jest 在uniapp中进行单元测试
目录
- 前情
- 安装依赖
- package.json配置
- jest配置
- 测试文件目录
- 编写setup.js
- 编写第一个测试文件
- jest.fn()和jest.spyOn()
- jest 解析scss失败
- 测试vuex
- $refs
- 定时器
- 测试函数调用n次
- 手动调用生命周期
- 处理其他模块导入的函数
- 测试插槽
前情
- uniapp推荐了测试方案
@dcloudio/uni-automator,属于自动化测试,api提供的示例偏重于渲染组件,判断当前渲染的组件是否和预期一致 - vue推荐的测试方案
vue test utils,属于单元测试,可以搭配jest、mocha等单测运行器
我选了方案2🕶️
关于vue的组件测试,vue官方提到:
你的 Vue 应用中大部分内容都应该由组件测试来覆盖,我们建议每个 Vue 组件都应有自己的组件测试文件。
当进行测试时,请记住,测试这个组件做了什么,而不是测试它是怎么做到的对于 视图 的测试:根据输入 prop 和插槽断言渲染输出是否正确。
对于 交互 的测试:断言渲染的更新是否正确或触发的事件是否正确地响应了用户输入事件
本身的测试写起来很简单,就是挺多东西需要配置的,比较麻烦,记录在后文
安装依赖
- @vue/test-utils
vue2项目安装:
npm install --save-dev @vue/test-utils@1
不指定的话默认安装最新,适合vue3项目吧 - jest
- vue-jest:为了处理.vue文件
npm install --save-dev @vue/vue2-jest@29(最后写jest版本) - babel-jest
- jest-environment-jsdom
jest版本在27以上,是要安装jest-environment-jsdom的
其他版本下如果报错:
[vue-test-utils]: window is undefined, vue-test-utils needs to be run in a browser environment. You can run the tests in node using jsdom
可以尝试:
npm install --save-dev jsdom jsdom-global
// 在测试的设置 / 入口中
require('jsdom-global')()
package.json配置
加一条就好
"scripts": {"test": "jest"
},
jest配置
可以配在package.json的jest选项中
也可以新建jest.config.js,我选了后者
module.exports = {moduleFileExtensions: ['js','vue'],transform: {'^.+\\.vue$': '<rootDir>/node_modules/@vue/vue2-jest','^.+\\.js$': '<rootDir>/node_modules/babel-jest'},moduleNameMapper: { // webpack中设置了别名,@设置为/src 的别名,就需要配这个'^@/(.*)$': '<rootDir>/src/$1'},testMatch: ['**/__tests__/**/*.spec.js'],transformIgnorePatterns: ['<rootDir>/node_modules/'],testEnvironment: "jsdom" // jest v27以上要加
}
⚠️:
官网提到的一个注意点:
如果你使用了 Babel 7 或更高版本,
你需要在你的 devDependencies 里添加 babel-bridge
($ npm install --save-dev babel-core@^7.0.0-bridge.0)。
我在运行时有相关的报错提示,所以我也按照这样安装了
如果你也有的话,可以参考一下
测试文件目录
新建__tests__目录,放在src目录下可以,根目录下也可以
(注意别少打s)
目录下的测试文件扩展名应该是.spec.js或者test.js ,我选了前者
这个也可以改,想改去找jest文档————
编写setup.js
通常用于执行一些全局的设置或引入一些测试所需的全局依赖,以确保这些设置和依赖在所有测试文件中都可用!
jest.config.js中新加一条:
setupFiles: ["./__tests__/setup.js"]
__test__文件夹下新建setup.js
1.项目中用到的uni或者wx的api是识别不了的,所以放在这里预先配置一下
2.在Vue.prototype上挂载的比如$toast、$api,$store、直接用this.调用的时候也是识别不了的,也要在这配置一下
localVue可以理解成创建本地的vue实例,可以使用localVue.prototype挂载一些东西而不会污染到真正的Vue.prototype,我在这挂到全局了,实际上可以在每个单独的测试文件中都create新的
import { createLocalVue } from "@vue/test-utils";
import Vuex from 'vuex'
import axios from 'axios'const CODE = '用户登录凭证';
// 创建的一个 Vue 的本地拷贝
const localVue = createLocalVue()localVue.use(Vuex)
const store = new Vuex.Store({state: {},mutations: {login: jest.fn()}
})
localVue.prototype.$store = store
localVue.prototype.$toast = jest.fn()
// 后面很多场景的使用是const {confirm} = await this.$modal(xxx), 这里直接模拟cofirm为true
localVue.prototype.$modal = jest.fn(() => Promise.resolve({ confirm: true }))
localVue.prototype.$api = {student: {studentLogin: jest.spyOn(axios, 'post')},
}global.uni = {showLoading: jest.fn(),hideLoading: jest.fn(),navigateTo: jest.fn(),switchTab: jest.fn(),getStorageSync: jest.fn(),setStorageSync: jest.fn(),login: jest.fn(() => Promise.resolve([,CODE]))
}
global.setValue = (target, value) => {target.element.value = valuetarget.trigger('input')
}
global.wx = global.uni
global.localVue = localVue
ps:这里挂了一个全局的方法setValue,因为官方的那个我使用会报错显示没有setValue(),查看setValue(),不知道是不是因为我的input是小程序的🤔
编写第一个测试文件
对组件StudentLogin.vue,新建studentLogin.spec.js
变更一个响应式属性之后,为了断言这个变化,测试需要等待 Vue 完成更新,可以
- await vm.nextTick() 2. await 操作,比如trigger
import { shallowMount } from "@vue/test-utils";
import StudentLogin from '@/pages/student-login/student-login'const TEST_VALUE = '123456'const TEST_TIP = {NO_NUMBER: '请填写学号!',NO_PASSWORD: '请填写密码!'}
// describe(name, fn): 表示一组测试,如果没有describe,那整个测试文件就是一个describe。name是这组测试的名字,fn是这组测试要执行的函数。
describe('StudentLogin.vue', () => {let wrapper;beforeEach(() => {// shallowMount和mount区别在于不会挂载子组件,比较适合单元测试,子组件的测试逻辑单独写wrapper = shallowMount(StudentLogin, {localVue})})// formSubmit触发时,输入账号没输入密码,提示请填写密码!test('if formSubmit triggered with number but no password, show tip', async () => {setValue(wrapper.find('input[name="number"]'), TEST_VALUE)await wrapper.vm.$nextTick();await wrapper.find('.submit-btn').trigger('click')expect(localVue.prototype.$toast).toBeCalledWith('error', TEST_TIP.NO_PASSWORD)})// formSubmit调用后,应该发起请求it('if formSubmit done, send request', async () => {setValue(wrapper.find('input[name="number"]'), TEST_VALUE)setValue(wrapper.find('input[name="password"]'), TEST_VALUE)await wrapper.vm.formSubmit()expect(localVue.prototype.$api.student.studentLogin).toBeCalled();expect(localVue.prototype.$api.student.studentLogin).toBeCalledWith(TEST_VALUE, TEST_VALUE, CODE)})// 销毁所有被创建的 Wrapper 实例enableAutoDestroy(afterEach)
})
jest.fn()和jest.spyOn()
承接上文:
轻轻记录一下jest.fn()和jest.spyOn()
他们都是用来模拟函数的行为,都会跟踪函数的调用和传参
区别:jest.fn()是创建一个全新的模拟函数,jest.spyOn()一般是模拟对象上的现有方法
比如
页面需要axios发请求,但是我们测试的时候不需要实际调用,
就可以利用
localVue.prototype.$api = {student: {studentLogin: jest.spyOn(axios, 'post')},
}
使用场景非常多,后文也会涉及
他们两返回的其实就是mockFn,在jest官网有非常多对mockFn的操作
指路:mockFn
我常用的一个mockFn.mockResolvedValue(value)
例如:
测试这个函数,是否成功发送请求,但我们无需发送真的请求,就可以模拟返回值
async getList() {const { data } = await this.$api.student.getData()this.list = data
}
// test.spec.js
test('', async () => {localVue.prototype.$api.student.getData.mockResolvedValue({list: [1,2,3]})await wrapper.vm.getList()expect(wrapper.list.length).toBe(3)
})
⚠️提醒一下自己,注意:
比如说我们要断言,trigger某个操作或者更新了页面之后,某个函数应该要被调用
会使用
const spy = jest.spyOn(wrapper.vm, 'someFunction')
expect(spy).toBeCalled()
但要注意这个必须要写在更新操作之前,如果写在之后是会断言错误的
👇 jest.spyOn写在了trigger之后,也就是开始跟踪的时候已经触发完了,
那么expect(infoSpy).toBeCalled()就会失败
test('if term picker triggered', async () => {const picker = wrapper.findComponent('picker')await picker.trigger("change", 1);const infoSpy = jest.spyOn(wrapper.vm, 'getInfo')expect(wrapper.vm.termIndex).toBe(1)expect(infoSpy).toBeCalled()})
jest 解析scss失败
比如这个页面有引入scss:
import { THEME_COLOR } from "@/uni.scss";
如果不做配置的话就会报错
解决方法:
新建一个styleMock.js
// styleMock.js
module.exports = {process() {return {code: `module.exports = {};`,};},
};
然后在jest.config.js中配置transform:
transform: {'^.+\\.vue$': '<rootDir>/node_modules/@vue/vue2-jest','^.+\\.js$': '<rootDir>/node_modules/babel-jest','\\.(css|less|scss|sass)$': '<rootDir>/styleMock.js',
},
然后运行npm run test,如果还是没生效,可以试试关闭编辑器重新启动
测试vuex
这里不提官网有的部分,有需要可自查
在组件中测试vuex
目前场景是这个组件在计算属性中使用了mapState
computed: {... mapState(['flag', 'userInfo'])
}
然后当userInfo.level = 1 && flag = 1时候要渲染某容器,我需要测试这个,那么就需要修改state中的数据
由于前面在setup.js中已经在localVue上安装了vuex,这里就通过localVue来访问
localVue.prototype.$store.state.flag = 1
localVue.prototype.$store.state.userInfo = { level: 1 }
不要用store.commit(),不生效
⚠️:更改完数据后,要等待页面更新,记得await nextTick()一下,否则断言会失败
$refs
类似于这样的代码:
close() { // 清除校验结果this.$refs.form.clearValidate();this.$emit('close');
},
this.$refs.form.clearValidate();会报错,提示找不到clearValidate这个function
解决方法1: 模拟一个form塞在stubs里
// 这里要写的是组件的名字,不是ref设置的名字const UniForms = {render: jest.fn(),methods: {validate: () => {},clearValidate:() => {}}}
wrapper = shallowMount(ForgetPassword, {localVue,stubs: {UniForms}
})
(模板上<uni-forms ref="form"></uni-forms>)
但我这个例子用这个方法不太行,会影响我别的测试(一些元素渲染失败,wrapper.find时会找不到)
先记录在这吧
解决方法2:
加一行👇
wrapper.vm.$refs.form.clearValidate = jest.fn()
如果有要返回的数据,可以在jest.fn()中直接模拟
比如说:
我们需要拿到返回的password、email,简单的jest.fn()无法满足需求
const { password, email } = await this.$refs.form.validate();
设定jest.fn()模拟的函数,返回成功值
wrapper.vm.$refs.form.validate = jest.fn(() => Promise.resolve({ password: 1, email: 1
}))
后续:
又有一处用到$refs:
mounted() { this.$refs.form.setRules(this.formRules);
}
这次是在mounted()里使用,方法2就用不了了,因为需要先mount(wrapper),才能拿到wrapper.vm,但这里又是要在mounted中执行的,假如我们使用wrapper.vm.$refs.form.setRules = jest.fn()其实就已经晚了,mounted已经执行完了
这个时候就可以用方法1~
定时器
检验有关定时器的方法
setTime(number) {this.codeText = `倒计时${number}s`;if(!number) {this.codeText = '发送验证码';this.isSending = false;this.timer = null;return;} else {number--;}this.timer = setTimeout(() => {this.setTime(number);}, 1000);
},
使用jest.useFakeTimers()指定全局使用假的定时器api
jest.advanceTimersByTime(1000)模拟时间快进1s
jest.useFakeTimers()
const sendCodeBtn = wrapper.findComponent('.send-code')
test('if setTime triggered 60, change btn content and start countdown', async () => {const setTimeSpy = jest.spyOn(wrapper.vm, 'setTime')await wrapper.vm.setTime(60)expect(sendCodeBtn.text()).toBe('倒计时60s')// 过一秒jest.advanceTimersByTime(1000)expect(setTimeSpy).toBeCalledWith(60 - 1)})test('if setTime triggered 0, change btn content and close timer', async () => {await wrapper.vm.setTime(0)expect(sendCodeBtn.text()).toBe('发送验证码')// 过一秒jest.advanceTimersByTime(1000)expect(wrapper.vm.timer).toBe(null)})
测试函数调用n次
本来想测
1.titleInput或contentInput无内容时 => 提示’请输入必要内容’
2.titleInput和contentInput都有内容时 => 不显示提示
(错误写法👇)
test("", async () => {await form.trigger('submit')expect(localVue.prototype.$toast).toHaveBeenCalledWith('none', '请输入必要内容')setValue(titleInput, TEST_VALUE)await form.trigger('submit')expect(localVue.prototype.$toast).toHaveBeenCalledWith('none', '请输入必要内容')setValue(contentInput, TEST_VALUE)await form.trigger('submit')expect(localVue.prototype.$toast).not.toHaveBeenCalled()
});
但上面这种写法是错的,实际上localVue.prototype.$toast的调用是累积的,不是相互隔离的,第三次expect(localVue.prototype.$toast)的时候实际上已经被调用三次了,那么not.toHaveBeenCalled()就不可能通过测试
这时候应该使用toHaveBeenNthCalledWidth(),第一个参数写n,表示第n次
第三次的时候不应该被调用,就用toHaveBeenCalledTimes()判断总调用次数
test("", async () => {await form.trigger('submit')expect(localVue.prototype.$toast).toHaveBeenNthCalledWith(1, 'none', '请输入必要内容')setValue(titleInput, TEST_VALUE)await form.trigger('submit')expect(localVue.prototype.$toast).toHaveBeenNthCalledWith(2, 'none', '请输入必要内容')setValue(contentInput, TEST_VALUE)await form.trigger('submit')expect(localVue.prototype.$toast).not.toHaveBeenCalledTimes(3);
});
手动调用生命周期
比如说
(onLoad是小程序里的生命周期)
onLoad({code}) {this.code = +code;// 每10min刷新一次if(!this.code) {this.getSignInCode();this.timer = setInterval(() => { this.getSignInCode() }, 600000);}
}
这里想测试code为0的时候是否调用了函数getSignInCode,且过了10min是否再次调用
我想手动调用onLoad(),onLoad并不在wrapper.vm上,不能通过wrapper.vm.onLoad访问
可以通过两种方式找到:(这个组件名叫ShowQRcode)
ShowQRcode.onLoad({ code: 1 })wrapper.vm.$options.onLoad({ code: 1 })
但都会报错:this.getSignInCode is not a function,因为getSignInCode是在wrapper.vm上的,所以这里要更改this指向
ShowQRcode.onLoad.call(wrapper.vm, {code: 0 })
test('', () => {const signInSpy = jest.spyOn(wrapper.vm, 'getSignInCode')ShowQRcode.onLoad.call(wrapper.vm, { code: 0 })expect(signInSpy).toHaveBeenCalledTimes(1)jest.advanceTimersByTime(600000)expect(signInSpy).toHaveBeenCalledTimes(2)})
处理其他模块导入的函数
场景:
import { uploadImg } from '@/util/uploadImg.js';async selectImg(res) {// 上传图片const { url } = await uploadImg(res.tempFilePaths, 'files')this.imgPaths.push(url[0]);
}
如果要测试selectImg(),当执行到uploadImg()就会报错
我们就可以利用jest.mock来模拟这个模块
记录一下jest.mock的简单使用:
官网的例子:
// banana.js
export default () => 'banana';
// test.spec.js
// 后续的测试中,任何导入./banana模块的代码将会被自动模拟,而不是实际的banana.js模块
jest.mock('./banana');
// 这个导入的bannana就被自动模拟了
const banana = require('./banana');
// 不会返回banana,因为被模拟了,默认返回undefined
banana(); // will return 'undefined'
还可以接收一个函数,显式指定模块导出的内容
// 相当于
// const mockFn = jest.fn(() => 'bannana'
// export default mockFn
jest.mock('./bannana', () => {return jest.fn(() => 'bannana');
});const bannana = require('./bannana');
bannana(); // Will return 'bannana';
所以这里就这样写:
// 相当于
// export const uploadImg = jest.fn(() => Promse.resolve({ data: TEST_UPLOAD_RESPONSE}))
jest.mock('@/util/uploadImg.js', () => ({otherFunction: xxx,uploadImg: jest.fn(() => Promise.resolve({ data: TEST_UPLOAD_RESPONSE }))
}));
测试插槽
项目比较简单,用插槽的地方很少,甚至没用到作用域插槽
这里只记录最简单的方法
官网是有例子的:测试插槽
就是在shallowMount的时候配置slots
beforeEach(() => {wrapper = shallowMount(Detail, {localVue,slots: {list: '<view>list</view>',operation: '<view>operation</view>'}})})
这里slots配置的就是模拟传入插槽的内容
比如list: '<view>list</view>',就是该组件内有一个插槽出口<slot name="list"></slot>
然后我们模拟传入这个插槽的内容是<view>list</view>
之后打印wrapper.html()会发现插槽出口确实都被替换成了我们预设的内容
只需要断言expect(wrapper.html()).toContain('<view>list</view>')即可完成测试
这里还出现一个问题,我有一个插槽出口长这样👇
<slot name="top"><view class="top__button" v-if="flag === 'xxx'"><text>{{ xxx }}</text></view>
</slot>
在插槽中指定了默认内容,且默认内容要通过v-if控制显示隐藏
并且这个地方我也写了一个测试,是测试top__button的显隐
如果我一开始预设的时候,预设了插槽top的内容,就会导致这个测试失败,因为找不到top__button了,直接被替换成了我预设的内容
其实失败的原因是我两个测试共用了一个wrapper的配置(习惯写在beforeEach里)
解决的方法就是在这个测试中,单独的再重新创建一个wrapper,不要预设slots就好
补充:
测试作用域插槽
参考:
https://juejin.cn/post/7119314584371986468?searchId=2023092122585499D5137C15C4283D9452
https://blog.csdn.net/pk142536/article/details/122255192
https://zhuanlan.zhihu.com/p/457648810
相关文章:
记录使用vue-test-utils + jest 在uniapp中进行单元测试
目录 前情安装依赖package.json配置jest配置测试文件目录编写setup.js编写第一个测试文件jest.fn()和jest.spyOn()jest 解析scss失败测试vuex$refs定时器测试函数调用n次手动调用生命周期处理其他模块导入的函数测试插槽 前情 uniapp推荐了测试方案dcloudio/uni-automator&…...
《C和指针》笔记30:函数声明数组参数、数组初始化方式和字符数组的初始化
文章目录 1. 函数声明数组参数2. 数组初始化方式2.1 静态初始化2.2 自动变量初始化 2.2 字符数组的初始化 1. 函数声明数组参数 下面两个函数原型是一样的: int strlen( char *string ); int strlen( char string[] );可以使用任何一种声明,但哪个“更…...
VBA技术资料MF64:遍历单元格搜索字符并高亮显示
【分享成果,随喜正能量】不要在乎他人的评论,不必理论与他人有关的是非,你只要做好自己就够了。苔花如米小,也学牡丹开。无论什么时候,都要有忠于自己的勇气,去做喜欢的事,去认识喜欢的人&#…...
一键智能视频编辑与视频修复算法——ProPainter源码解析与部署
前言 视频编辑和修复确实是随着电子产品的普及变得越来越重要的技能。有许多视频编辑工具可以帮助人们轻松完成这些任务如:Adobe Premiere Pro,Final Cut Pro X,Davinci Resolve,HitFilm Express,它们都提供一些视频修…...
Flutter开发环境的配置
2023-10最新版本 flutter SDK版本下载地址 https://flutter.cn/docs/development/tools/sdk/releases gradle各版本快速下载地址 https://blog.csdn.net/ii950606/article/details/109105402 JAVA SDK下载地址 https://www.oracle.com/java/technologies/downloads/#java…...
【超详细】Wireshark教程----Wireshark 分析ICMP报文数据试验
一,试验环境搭建 1-1 试验环境示例图 1-2 环境准备 两台kali主机(虚拟机) kali2022 192.168.220.129/24 kali2022 192.168.220.3/27 1-2-1 网关配置: 编辑-------- 虚拟网路编辑器 更改设置进来以后 ,先选择N…...
Linux命令(92)之rm
linux命令之rm 1.rm介绍 linux命令rm是用来删除一个或多个文件/目录,由于其删除的不可逆性,建议在日常工作中一定要慎用 2.rm用法 rm [参数] 文件/目录 rm常用参数 参数说明-r递归删除文件或目录-f不提示强制删除-i删除文件或目录前进行确认-v详细显…...
Mysql主从复制数据架构全面解读
大家好,我是山子,今天给大家分析Mysql 实现主从复制的方方面面,主从复制当然也是我们做读写分离的前提,以下内容是从各网络平台摘录整理总结归纳在一起的;内容已经从主从复制的各方面的维度进行了阐述;非常…...
ios证书类型及其作用说明
ios证书类型及其作用说明 很多刚开始接触iOS证书的开发者可能不是很了解iOS证书的类型功能和概念。下面对iOS证书的几个方面进行介绍。 apple开发账号分类: 免费账号: 无需支付费用给apple,使用个人信息注册的账号 可以开发测试安装&…...
警告-Ubuntu提示W: Possible missing firmware xxx解决方法
目录 现象原因解决方法 现象 当执行 sudo apt-get update或者sudo apt-get dist-upgrade时,有如下警告: W: Possible missing firmware /lib/firmware/rtl_nic/rtl8125a-3.fw for module r8169 W: Possible missing firmware /lib/firmware/rtl_nic/rt…...
有时候,使用 clang -g test.c 编译出可执行文件后,发现 gdb a.out 进行调试无法读取符号信息,为什么?
经过测试,gdb 并不是和所有版本的 llvm/clang 都兼容的 当 gdb 版本为 9.2 时,能支持 9.0.1-12 版本的 clang,但无法支持 16.0.6 版本的 clang 可以尝试使用 LLVM 专用的调试器 lldb 我尝试使用了 16.0.6 版本的 lldb 调试 16.0.6 的 clan…...
UG\NX二次开发 信息窗口的一些操作 NXOpen/ListingWindow
文章作者:里海 来源网站:王牌飞行员_里海_里海NX二次开发3000例,里海BlockUI专栏,C\C++-CSDN博客 简介: UG\NX二次开发 信息窗口的一些操作 NXOpen/ListingWindow 效果: 代码: #include "me.hpp" #include <NXOpen/ListingWindow.hxx> #include <…...
macbook电脑磁盘满了怎么删东西?
macbook是苹果公司的一款高性能笔记本电脑,受到很多用户的喜爱。但是,如果macbook的磁盘空间不足,可能会导致一些问题,比如无法开机、运行缓慢、应用崩溃等。那么,macbook磁盘满了无法开机怎么办,macbook磁…...
解释 RESTful API,以及如何使用它构建 web 应用程序
RESTful API是一种基于HTTP协议,使用REST架构风格设计的API。其核心思想是将所有的Web应用程序资源抽象为一组资源集合,并通过HTTP协议中的GET、POST、PUT、DELETE等几个方法对这些资源进行操作,使得Web应用程序能够方便地、高效地进行管理和…...
qml使用c++自定义listmodel数据
qml要使用c中自定义的model,首先该model类需要继承QAbstractListModel类,然后需要重写其中的三个函数,分别是 int rowCount(const QModelIndex &parent); QVariant data(const QModelIndex &index, int role Qt::DisplayRole); QHas…...
cf 解题报告 01
E. Power of Points Problem - 1857E - Codeforces 题意: 给你 n n n 个点,其整数坐标为 x 1 , … x n x_1,\dots x_n x1,…xn,它们位于一条数线上。 对于某个整数 s s s,我们构建线段[ s , x 1 s,x_1 s,x1], [ s , x…...
傅里叶系列 P1 的定价选项
如果您想了解更多信息,请查看第 2 部分和第 3 部分。 一、说明 这是第一篇文章,我将帮助您获得如何使用这个新的强大工具来解决金融中的半分析问题并取代您的蒙特卡洛方法的直觉。 我们都知道并喜欢蒙特卡洛数字积分方法,但是如果我告诉你你可…...
第二十届北京消防展即将开启,汉威科技即将精彩亮相
10月10日~13日,第二十届中国国际消防设备技术交流展览会,将在北京市顺义区中国国际展览中心新馆隆重举行。该展会由中国消防协会举办,是世界三大消防品牌展会之一,本届主题为“助力产业发展,服务消防救援”。届时将有4…...
mongodb、mysql、redis 区别
MongoDB、MySQL 和 Redis 是三种不同的数据库管理系统,它们在数据存储、访问模型和使用场景方面有一些显著的区别。 1. 数据存储模型: MongoDB:MongoDB 是一种文档数据库,它使用 BSON(Binary JSON)格式来存储数据。数据以文档的形式组织,每个文档可以有不同的字段,文档…...
【Flutter】Flutter Web 开发 如何从 URL 中获取参数值
【Flutter】Flutter Web 开发 如何从 URL 中获取参数值 文章目录 一、前言二、Flutter Web 中的 URL 处理三、如何从 URL 中获取参数四、实际业务中的用法五、完整示例六、总结 一、前言 大家好!我是小雨青年,今天我想和大家分享一下在 Flutter Web 开发…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...
NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
