enzymejest TDD与BDD开发实战
一、前端自动化测试需要测什么
1. 函数的执行逻辑,对于给定的输入,输出是否符合预期。
2. 用户行为的响应逻辑。
- 对于单元测试而言,测试粒度较细,需要测试内部状态的变更与相应函数是否成功被调用。
- 对于集成测试而言,测试粒度较粗,一般测试ui展示上的变更(文本内容改变、组件类别改变等)。
3. 快照测试。对于不需要经常修改dom结构的组件,我们会存储一个快照,如果在后续的版本中修改了dom结构,测试用例会不通过,需要确认更新快照。
二、为什么需要自动化测试
你或许会疑惑,如果我们做的是一些业务的开发,而不是工具类函数的开发,似乎手动测试就可以满足需求。对于用户行为的响应逻辑可以通过点击来测试,dom结构的变更也可以通过肉眼观察。测试用例的代码可能甚至比业务代码量大,那前端有必要耗时耗力的进行自动化测试吗?
长期来看,集成自动化测试是有必要的。
1. 有利于回归测试。在公司项目中产品是经常迭代的,当我们修改了A功能,就需要测试相关联的B功能不受影响。如果没有自动化测试,每一次回归测试都需要手动进行,且并不能保证你没纳入考虑范围的C功能是不受影响的。自动化测试有利于降低回归测试的成本,并提高程序员的安全感。
2. 有利于代码重构。跟上一点类似,我们需要保证重构前后的预期是一致的,这时候我们就可以先对于老代码编写测试用例,使测试用例能够全部通过。再重构业务代码,如果重构后的代码也能通过全部的测试用例,那么代码的可靠性是较高的。如果采用手动测试的方案,那么你的执行流可能是:重构A功能->测试A功能->重构B功能->测试A和B功能。因为重构前的代码架构通常会混乱一点,所以为了确保后重构的功能不影响先重构的功能,手动测试的工作量会越来越大。
3. 有利于代码优化。当测试用例通过后就可以放心的进行代码优化了,省去了每次代码优化完手动测试的成本。
4. 前端开发与后端接口解耦。假设后端接口还未开发完成,当我们和后端约定好数据结构后,就可以模拟后端接口返回的数据并进行测试。当然,我们也可以选择在业务代码里写死数据并进行手动测试,但这就意味着后续需要修改业务代码;或者选择用抓包工具模拟响应结果进行手动测试,这种不需要修改业务代码,但是需要权衡手动测试和自动化测试的成本。
三、TDD与BDD
测试驱动开发(Test-Driven Development)的流程如下:
1. 根据要实现的功能编写测试用例,测试用例不通过
2. 实现相关功能,测试用例通过
3. 优化代码,完成开发
由于测试用例不通过时会显示红色,测试用例通过后会显示绿色,所以测试驱动开发又称Red-Green-Development。
测试驱动开发的优点如下:
1. 实现代码前先编写测试用例,确保代码一定是易于测试的,在开发视角的基础上扩展了测试视角,代码的组织架构会更好。
2. 如果测试用例有误,可能在实现功能前后均可以通过测试。由于我们先编写测试用例后开发,如果测试用例在开发前就通过,那么大概率测试用例是有问题的,我们就可以及时发现修改。降低了编写出错误测试代码的可能性。
3. 自动化测试的通用优点。
行为驱动开发(Behavior-Driven Development)一般是测试驱动开发的自然延伸,它的核心在于根据用户行为来设计测试用例,对于是否需要在开发前编写测试用例没有强制要求。
四、单元测试与集成测试
单元测试的优点: 测试粒度细,代码覆盖率高,运行速度快。
单元测试的缺点:
1. 代码量大 。
2. 关注代码实现细节,与业务代码耦合度高。
3. 每个单元的单元测试通过,也无法保证集成后能正常运行。比如说A组件给B组件传入data,A组件测试传入的data结构为对象,B组件测试接收到的data为数组,两个组建的测试用例都能通过,但是集成后运行就会由于数据结构不一致报错。
单元测试的适用场景:工具库。
集成测试的优点:
1. 测试粒度没那么细,比所有单元的单元测试代码量总和小。
2. 不关注代码实现细节,只关心展示给用户的结果,业务代码耦合度较低。
3. 集成测试能确保单元能够协作运行,通过集成测试通常来讲系统对于用户能够正常运行。
集成测试的缺点:
1. 集成测试测试可能不如单元测试细致。
2. 集成测试需要运行多个组件,测试速度会慢一些。
集成测试的适用场景:业务系统。
五、具体实现
单元测试、集成测试、TDD、BDD之间要怎么集成其实没有标准答案,而且BDD和TDD本身也不是对立的概念。只是BDD本身是以用户的“故事”为导向的,这些故事通常涉及不止一个单元所以BDD通常和集成测试结合,单元测试与TDD结合。
1. 单元测试与TDD
用react脚手架创建出项目后,由于enzyme官方没有适配react17及以上版本,需要将react版本降级为16。如果需要用react17及以上的版本,可以用非官方的适配器,或者改用react-testing-library。但是react-testing-library本身不关注代码实现细节,而是以用户视角触发的,所以我个人感觉不是很适合做单元测试。
npm install react@16 react-dom@16 --save
npm install enzyme enzyme-adapter-react-16 --save-dev
我们做一个简单的todoList项目,当我们在输入框中输入内容,按下回车后就会展现在下方。点击项尾的删除键可以进行删除。
我们将这个项目拆分为Header组件和UndoList组件。
我们在src目录下创建如下结构,__tests__/unit目录下编写单元测试代码,TodoList目录下编写业务代码。
因为采用TDD的模式开发,所以先来编写测试代码。
环境准备
enzyme与react集成需要配置适配器,由于这个配置需要在每个测试文件最开始引入,所以我们可以将其抽离到一个单独的文件中,然后对jest进行配置,让jest在测试环境准备好后执行该文件。
react内部其实已经对jest进行了配置,我们需要做的就是将其暴露出来。
// npm run eject的执行前提是没有未追踪的文件,所以我们需要先初始化git仓库并提交
git init
git add .
git commit -m "init resposity"
npm run eject
在运行完npm run eject后,我们可以发现package.json文件有新增的配置项Jest,配置项中有一个属性是setupFilesAfterEnv。
"jest": {// ..."setupFilesAfterEnv": ["<rootDir>/src/setupTests.js"],// ...},
可以看出,setupTests文件会在测试环境准备好后执行,所以我们只需要新增文件setupEnzyme.js,修改jest配置项,并将enzyme的适配器配置填入该文件即可。
"jest": {// ..."setupFilesAfterEnv": ["<rootDir>/src/setupTests.js","<rootDir>/src/setupEnzyme.js"],// ...},
// src/setupEnzyme.jsimport Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';Enzyme.configure({ adapter: new Adapter() });
由于jest本身不支持TextEncoder、TextDecoder、ReadableStream,而在enzyme内部又会调用到相应的方法,所以我们需要在import Enzyme前将方法挂载在global上。
// setupTests.jsimport { TextEncoder, TextDecoder, ReadableStream } from 'util';
global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;
global.ReadableStream = ReadableStream;
测试用例编写
- Header
header组件的功能是点击回车键时,能将数据传送给TodoList,我们将header组件设计成受控组件(即组件内的状态会根据用户输入实时更新)。对功能进行拆解如下:
1. 输入框的展示值为state.inputData,state.inputData初始化为空。
2. input框输入内容时,state.inputData随之改变。
3. 当输入框不为空时,用户敲击回车后,调用props.addUndoItem,state.inputData清空。
4. 当输入框为空时,用户敲击回车后,不调用props.addUndoItem.
4. 快照测试。
test('输入框的展示值为state,state.inputData初始化为空', () => {const wrapper = shallow(<Header />);const input = wrapper.find("[data-test-id='input']");expect(input.prop('value')).toBe(wrapper.state('inputData'));expect(wrapper.state('inputData')).toBe('');
})
由于我们还未编写业务代码,运行npx jest时,测试用例是不通过的。
业务代码编写
由于enzyme只能追踪到类组件里的状态,所以这里我们创建类组件。
如果你需要用函数组件,可以参考Testing React Hook State Changes - DEV Community,他的核心思路是相信react,只要我们调用了setState方法,传入了正确的参数,就认为状态可以被正确的修改。通过mock setState方法,来判断调用了函数并传入了正确的参数。
import React, { Component } from "react";export default class Header extends Component {state = {inputData: "",};render() {return (<div><input data-test-id="input" value={this.state.inputData} /></div>);}
}
再次运行测试,测试通过。
然后是第2-4个测试用例及业务代码。
test('输入框输入字符时,state.inputData随之改变', () => {const wrapper = shallow(<Header />);const input = wrapper.find("[data-test-id='input']");const inputData = "hello world";input.simulate('change', { target: { value: inputData } });expect(wrapper.state('inputData')).toBe(inputData);
})
import React, { Component } from "react";export default class Header extends Component {state = {inputData: "",};render() {return (<div><inputdata-test-id="input"value={this.state.inputData}onChange={(e) => this.setState({ inputData: e.target.value })}/></div>);}
}
test('当输入框不为空时,用户敲击回车后,调用props.addUndoItem,state.inputData清空', () => {const func = jest.fn();const wrapper = shallow(<Header addUndoItem={func} />);const inputData = "hello world";wrapper.setState({ inputData });const input = wrapper.find("[data-test-id='input']");input.simulate('keyUp', { keyCode: 13 });expect(func).toHaveBeenCalled();expect(func).toHaveBeenLastCalledWith(inputData);expect(wrapper.state('inputData')).toBe('');
})test('当输入框为空时,用户敲击回车后,调用props.addUndoItem,state.inputData清空', () => {const func = jest.fn();const wrapper = shallow(<Header addUndoItem={func} />);wrapper.setState({ inputData: '' });const input = wrapper.find("[data-test-id='input']");input.simulate('keyUp', { keyCode: 13 });expect(func).not.toHaveBeenCalled();
})
import React, { Component } from "react";export default class Header extends Component {state = {inputData: "",};handleKeyUp = (e) => {if (e.keyCode === 13 && this.state.inputData !== "") {this.props.addUndoItem(this.state.inputData);this.setState({ inputData: "" });}};render() {return (<div><inputdata-test-id="input"value={this.state.inputData}onChange={(e) => this.setState({ inputData: e.target.value })}onKeyUp={this.handleKeyUp}/></div>);}
}
header组件的逻辑编写完成,接下来我们补充完样式以后,就可以进行快照测试了。
先引入一下header。
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'))// App.jsx
import TodoList from './container/TodoList';
export default function App() {return <TodoList />
}// container/TodoList/index.jsx
import React, { Component } from "react";
import Header from "./Header";
export default class index extends Component {render() {return (<div><Header /></div>);}
}
运行npm run start,页面展示如下。
我们对样式进行优化,优化后页面展示如下。
// container/TodoList/style.css.header-wrapper {display: flex;align-items: center;justify-content: center;height: 100px;background-color: #e1d3d3;gap: 20px;
}.header-input {outline: none;line-height: 24px;width: 360px;border-radius: 5px;text-indent: 10px;
}.header-span {font-size: 24px;font-weight: bold;
}
// container/TodoList/Header.jsx
import React, { Component } from "react";export default class Header extends Component {state = {inputData: "",};handleKeyUp = (e) => {if (e.keyCode === 13 && this.state.inputData !== "") {this.props.addUndoItem(this.state.inputData);this.setState({ inputData: "" });}};render() {return (<div className="header-wrapper"><span className="header-span">TodoList</span><inputdata-test-id="input"className="header-input"value={this.state.inputData}placeholder="请输入待办项"onChange={(e) => this.setState({ inputData: e.target.value })}onKeyUp={this.handleKeyUp}/></div>);}
}
// container/TodoList/index.jsx
import "./style.css";
// ...
快照测试
test('快照测试', () => {const wrapper = shallow(<Header />);expect(wrapper).toMatchSnapshot();
})
运行测试用例后,会生成__snapshot__文件夹,后续修改样式/dom结构都会导致测试不通过。
剩下两个组件的流程不做赘述,编写完代码如下。
// __tests__/TodoList.jsimport { shallow } from "enzyme";
import TodoList from '../../index';let wrapper;
beforeEach(() => {wrapper = shallow(<TodoList />);
})test('快照测试', () => {expect(wrapper).toMatchSnapshot();
})test('state.undoList初始化为空', () => {expect(wrapper.state('undoList')).toEqual([]);
})test('向header传入addUndoItem方法,当该方法被调用时,更新state.undoList', () => {const header = wrapper.find("[data-test-id='header']");expect(header.prop('addUndoItem')).toBeTruthy();expect(header.prop('addUndoItem')).toEqual(wrapper.instance().addUndoItem);const prevUndoList = ['hello'];wrapper.setState({ undoList: prevUndoList });const inputData = 'world';wrapper.instance().addUndoItem(inputData);expect(wrapper.state('undoList')).toEqual([...prevUndoList, inputData]);
})test('向undoList组件传入list属性,属性值为state.undoList', () => {const undoList = wrapper.find("[data-test-id='undo-list']");expect(undoList.prop('list')).toBeTruthy();expect(undoList.prop('list')).toEqual(wrapper.state('undoList'));
})test('向undoList组件传入deleteUndoItem方法,当该方法被调用时,更新state.undoList', () => {const undoListData = ['hello', 'world'];wrapper.setState({ undoList: [...undoListData] });const undoList = wrapper.find("[data-test-id='undo-list']");expect(undoList.prop('deleteUndoItem')).toBeTruthy();expect(undoList.prop('deleteUndoItem')).toEqual(wrapper.instance().deleteUndoItem);wrapper.instance().deleteUndoItem(0);expect(wrapper.state('undoList')).toEqual([undoListData[1]]);
})
// TodoList/index.js
import React, { Component } from "react";
import Header from "./Header";
import "./style.css";
import UndoList from "./UndoList";export default class index extends Component {state = {undoList: [],};addUndoItem = (item) => {this.setState({undoList: [...this.state.undoList, item],});};deleteUndoItem = (index) => {const newUndoList = this.state.undoList;newUndoList.splice(index, 1);this.setState({ undoList: newUndoList });};render() {return (<div><Header data-test-id="header" addUndoItem={this.addUndoItem} /><UndoListdata-test-id="undo-list"list={this.state.undoList}deleteUndoItem={this.deleteUndoItem}/></div>);}
}
// __tests__/Undolist.js
import { shallow } from "enzyme";
import UndoList from "../../UndoList";test('快照测试', () => {const wrapper = shallow(<UndoList list={[]} />);expect(wrapper).toMatchSnapshot();
})test('props.list为空时,列表展示为空', () => {const wrapper = shallow(<UndoList list={[]} />);// console.log(wrapper.find("[data-test-id='list-item']"));expect(wrapper.find("[data-test-id='list-item']").length).toBe(0);
})test('props.list为不为空时,列表展示对应项', () => {const list = ['hello', 'world'];const wrapper = shallow(<UndoList list={list} />);const listItem = wrapper.find("[data-test-id='list-item']");expect(listItem.length).toBe(2);expect(listItem.at(0).text()).toBe('hello');expect(listItem.at(1).text()).toBe('world');
})test('点击删除按钮时,调用props.deleteUndoItem', () => {const list = ['hello', 'world'];const func = jest.fn();const wrapper = shallow(<UndoList list={list} deleteUndoItem={func} />);const deleteBtn = wrapper.find("[data-test-id='delete-btn']");deleteBtn.at(0).simulate('click');expect(func).toHaveBeenCalled();expect(func).toHaveBeenLastCalledWith(0);
})
// UndoList.jsx
import React, { Component } from "react";export default class UndoList extends Component {render() {return (<ul className="undo-list-wrapper">{this.props.list.map((item, index) => {return (<li key={index} className="undo-list-item"><div data-test-id="list-item">{item}</div><divdata-test-id="delete-btn"className="undo-delete-btn"onClick={() => this.props.deleteUndoItem(index)}>-</div></li>);})}</ul>);}
}
页面展示效果如下
我们运行npx jest --coverage来看一下测试覆盖率。
运行完命令后会新生成coverage文件夹,我们在浏览器打开index.html。
由于我们没给src/index.js和src/App.jsx编写测试用例,所以第一条显示为0。但是对于我们编写了测试用例的TodoList组件,可以看到测试的覆盖率是百分百,所以TDD这种开发模式的代码覆盖率是非常高的。
2. 集成测试与BDD
可以看出,在进行单元测试时,测试代码量是比较大的,如果单元内部的逻辑很复杂,那么测试代码量还会大幅增加。
而且,我们在单元测试中用了大量业务代码内的属性,像state、props等,这些其实对于用户来说是不可见的。那么,我们可不可以站在用户视角,以模拟用户行为的方式来进行黑盒测试呢?答案是肯定的。
站在用户角度,Todolist无非就干了三件事。
1. 待办项初始化为空。
2. 输入待办项后回车,待办项会被展示在最下方。
3. 点击删除按钮,对应的待办项被删除。
根据以上的用户故事,我们编写对应的测试代码。
import { mount } from 'enzyme';
import TodoList from '../../index';let wrapper;// 对于集成测试而言,我们需要渲染子组件,所以调用mount方法
// 对于mount方法,元素会被真正挂载在页面上,所以如果我们在两个测试用例里面分别创建了wrapper
// 且每个wrapper中有一个undoListItem
// 那么在不调用卸载方法的情况下,页面上会存在两个undoListItem
// 所以在集成测试里,我只创建了一次wrapper
beforeAll(() => {wrapper = mount(<TodoList />);
});test(`1. 用户进入网站2. 待办项显示为空`, () => {const undoListItem = wrapper.find("[data-test-id='list-item']");expect(undoListItem.length).toBe(0);
})test(`1. 用户输入待办项2. 用户敲击回车3. 待办项展示在下方`, () => {const input = wrapper.find("[data-test-id='input']");const inputData = "hello";input.simulate('change', { target: { value: inputData } });input.simulate('keyUp', { keyCode: 13 });const undoListItem = wrapper.find("[data-test-id='list-item']")expect(undoListItem.length).toBe(1);expect(undoListItem.text()).toBe(inputData);
});test(`1. 用户输入待办项2. 用户敲击回车3. 在原待办项下方新增待办项`, () => {const input = wrapper.find("[data-test-id='input']");const inputData = "world";input.simulate('change', { target: { value: inputData } });input.simulate('keyUp', { keyCode: 13 });const undoListItem = wrapper.find("[data-test-id='list-item']");expect(undoListItem.length).toBe(2);expect(undoListItem.at(1).text()).toBe(inputData);
});test(`1. 用户点击第一项的删除按钮2. 第一项被删除`, () => {const deleteBtn = wrapper.find("[data-test-id='delete-btn']");deleteBtn.at(0).simulate('click');const undoListItem = wrapper.find("[data-test-id='list-item']");expect(undoListItem.length).toBe(1);expect(undoListItem.text()).toBe("world");
})test(`1. 用户点击第一项的删除按钮2. 第一项被删除`, () => {const deleteBtn = wrapper.find("[data-test-id='delete-btn']");deleteBtn.at(0).simulate('click');const undoListItem = wrapper.find("[data-test-id='list-item']");expect(undoListItem.length).toBe(0);
})
因为我们已经实现了对应的业务代码,所以测试用例均可以正常通过。
可以看出,相比较对于一个个单元进行单元测试,整体编写集成测试的代码量是会更少的。如果后续我们修改了state里的数据结构,或者是props的属性名,只要最终展现在页面上的结果不变,那么集成测试都可以通过。
相关文章:
enzymejest TDD与BDD开发实战
一、前端自动化测试需要测什么 1. 函数的执行逻辑,对于给定的输入,输出是否符合预期。 2. 用户行为的响应逻辑。 - 对于单元测试而言,测试粒度较细,需要测试内部状态的变更与相应函数是否成功被调用。 - 对于集成测试而言&a…...
Linux菜鸟级常用的基本指令和基础知识
前言:很多Linux初学者都会头疼于指令太多记不住,笔者刚学习Linux时也是如此,学习Linux指令时,学了后面的指令,前面的指令也会忘的差不多了,针对于以上这些情况,笔者今天来分享一篇Linux菜鸟级的常用指令的博…...
Spark创建多种数据格式的DataFrame
假如我们要通过RDD[Row]创建一个包含多个列的DataFrame,重点是列的数据类型可能会包含多个,这时候需要有一点技巧。 | uid | user_name | age | income | |:----|:----------|:----|:-------| | 1111 | nituchao | 21 | 123.0 | 这个DataFrame里包含…...
C语言:指针
1. 什么是指针? 在C语言中,指针是一个变量,用于存储另一个变量的内存地址。指针不是直接保存值,而是保存数据所在的内存位置。 语法: type *pointer_name; 例如: int *ptr; 在这个例子中,pt…...
kafka生产者专题(原理+拦截器+序列化+分区+数据可靠+数据去重+事务)
目录 生产者发送数据原理参数说明代码示例(同步发送数据)代码示例(异步) 异步和同步的区别同步发送定义与流程特点 异步发送定义与流程特点 异步回调描述代码示例 拦截器描述代码示例 消息序列化描述代码示例(自定义序…...
谷歌2025年AI战略与产品线布局
在2024年12月的战略会议上,谷歌高层向员工描绘了2025年的宏伟蓝图,特别是在人工智能(AI)领域。这一年被定位为AI发展的关键转折点,谷歌计划通过一系列新产品和创新来巩固其在全球科技领域的领导地位。本文将深入探讨谷歌的2025年AI战略、重点产品以及竞争策略。 一、整体…...
Kernel Stack栈溢出攻击及保护绕过
前言 本文介绍Linux内核的栈溢出攻击,和内核一些保护的绕过手法,通过一道内核题及其变体从浅入深一步步走进kernel世界。 QWB_2018_core 题目分析 start.sh qemu-system-x86_64 \-m 128M \-kernel ./bzImage \-initrd ./core.cpio \-append "…...
QT-窗口嵌入外部exe
窗口类: #pragma once #include <QApplication> #include <QWidget> #include <QVBoxLayout> #include <QProcess> #include <QTimer> #include <QDebug> #include <Windows.h> #include <QWindow> #include <…...
38-其他地方使用模式
38-其他地方使用模式 模式除了可以在 match 表达式中使用外,还可以使用在变量定义(等号左侧是个模式)和 for in 表达式(for 关键字和 in 关键字之间是个模式)中。 但是,并不是所有的模式都能使用在变量定…...
才气小波与第一性原理
才气小波与第一性原理 才气小波与第一性原理具身智能云藏山鹰类型物热力学第二定律的动力机械外骨骼诠释才气小波导引社会科学概论软凝聚态数学意气实体过程王阳明代数Wangyangmingian王阳明算符才气语料库命运社会科学概论意气实体过程业务分层框架示例 才气小波与第一性原理 …...
104周六复盘 (188)UI
1、早上继续看二手书的一个章节,程序开发流程、引擎、AI等内容, 内容很浅,基本上没啥用,算是复习。 最大感触就是N年前看同类书的里程碑、AI相关章节时,会感觉跟自己没啥关系, 而如今则密切相关…...
CDP集群安全指南-动态数据加密
[〇]关于本文 集群的动态数据加密主要指的是加密通过网络协议传输的数据,防止数据在传输的过程中被窃取。由于大数据涉及的主机及服务众多。你需要更具集群的实际环境来评估需要为哪些环节实施动态加密。 这里介绍一种通过Cloudera Manager 的Auto-TLS功能来为整个…...
C# 设计模式:装饰器模式与代理模式的区别
C# 设计模式:装饰器模式与代理模式的区别 在软件设计中,装饰器模式(Decorator Pattern)和代理模式(Proxy Pattern)都是结构型设计模式,它们的目的都是通过对对象进行包装,来增加或改…...
[深度学习] 大模型学习1-大语言模型基础知识
大语言模型(Large Language Model,LLM)是一类基于Transformer架构的深度学习模型,主要用于处理与自然语言相关的各种任务。简单来说,当用户输入文本时,模型会生成相应的回复或结果。它能够完成许多任务&…...
GitLab集成Runner详细版--及注意事项汇总【最佳实践】
一、背景 看到网上很多用户提出的runner问题其实实际都不是问题,不过是因为对runner的一些细节不清楚导致了误解。本文不系统性的介绍GitLab-Runner,因为这类文章写得好的特别多,本文只汇总一些常几的问题/注意事项。旨在让新手少弯路。 二、…...
STM32-笔记34-4G遥控灯
4G接线 一、项目需求 服务器通过4G模块远程遥控开关灯。 二、项目实现 复制项目文件夹38-wifi控制风扇项目 重命名为39-4G遥控点灯 打开项目文件 加载文件 main.c #include "sys.h" #include "delay.h" #include "led.h" #include "ua…...
PHP 使用集合 处理复杂数据 提升开发效率
文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons:JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram,自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 ? 5 IDEA必装的插件&…...
AI代码开发实践-微信小程序开发
接上回,本人参加了一次小孩学校组织的护学岗,萌生了开发一个微信小程序的水印相机的想法,说干就干。 最近也是在学习用AI编程,索性之前也用一点,今天就尝试一下 工具选择,环境搭建 阿里-通义灵码 通义灵…...
【网络安全 | 漏洞挖掘】私有项目中的账户接管过程
未经许可,不得转载。 正文 该程序包含多个通配符目标。在逐一搜索后,我最终发现了一个具有 P4 严重级别的 IDOR 漏洞(不正确的直接对象引用),允许我删除其他用户在帖子中的评论。 其中一个目标是一个只有单个域名的网站,提供注册、登录和重置密码功能。我尝试寻找任何可…...
【算法不挂科】算法期末考试【选择题专项练习】<多单元汇总>
前言 大家好吖,欢迎来到 YY 滴算法不挂科系列 ,热烈欢迎! 本章主要内容面向接触过C的老铁 主要内容含: 一.选择题 【1】算法绪论 1.算法与程序的区别是( ) A.输出 B.输入 C.确定性 D.有穷性 D 2.算法复杂度分析的两种基本方法…...
分布式消息中间件有哪些知名的产品
首先是Apache Kafka,它是一个分布式流处理平台,专注于高性能、低延迟的实时数据管道和流应用。Kafka通过分区和复制机制实现了高可用性和容错性,支持消息的持久化存储和高效的数据传输。其强大的生态系统还提供了丰富的客户端库和集成工具&am…...
kubernets基础入门
首先通过Kubernets架构图来认识Kubernets各个功能组件之间是怎么相互关联配合工作的。 一、Kubernets架构图: 通过上图来介绍集群功能组件通信详细过程: kubernetes api server 作为集群的核心,负责集群各功能模块之间的通信。集群内的各个功…...
【数据库初阶】MySQL数据类型
🎉博主首页: 有趣的中国人 🎉专栏首页: 数据库初阶 🎉其它专栏: C初阶 | C进阶 | 初阶数据结构 亲爱的小伙伴们,大家好!在这篇文章中,我们将深入浅出地为大家讲解 MySQL…...
(九千七-星河襟)椭圆曲线加密(ECC, Elliptic Curve Cryptography)及其例题
椭圆曲线加密(ECC)是一种基于椭圆曲线数学的公钥加密技术。它提供了一种高效的加密方法,能够在较小的密钥长度下实现与传统加密算法(如RSA)相同的安全级别。以下是ECC的主要特点和工作原理的总结: 1. 基本…...
LookingGlass使用
背景 Looking Glass 是一款开源应用程序,可以直接使用显卡直通的windows虚拟机。 常见环境是Linux hostwindows guest,基本部署结构图: 编译 git clone --recursive https://github.com/gnif/LookingGlass.git编译client mkdir client/b…...
ArcGIS Server 10.2授权文件过期处理
新的一年,arcgis server授权过期了,服务发不不了。查看ecp授权文件,原来的授权日期就到2024.12.31日。好吧,这里直接给出处理方法。 ArcGIS 10.2安装时,有的破解文件中会有含一个这样的注册程序,没有的话&…...
js es6 reduce函数, 通过规格生成sku
const specs [{ name: 颜色, values: [红色, 蓝色, 绿色] },{ name: 尺寸, values: [S, M, L] } ];function generateSKUs(specs) {return specs.reduce((acc, spec) > {const newAcc [];for (const combination of acc) {for (const value of spec.values) {newAcc.push(…...
Spring 事务底层原理
61 张图,剖析 Spring 事务,就是要钻到底! 拜托!面试请不要再问我 Transactional my: AOP Transactional PlatformTransactionManager:数据源隔离 TransactionInterceptor:拦截添加了注解的…...
ruoyi 分页 查询超出后还有数据; Mybatis-Plus 分页 超出后还有数据
修改:MybatisPlusConfig 类中 分页合理化修改为:paginationInnerInterceptor.setOverflow(false);...
基于Java的超级玛丽游戏的设计与实现【源码+文档+部署讲解】
目 录 1、绪论 1.1背景以及现状 1.2 Java语言的特点 1.3 系统运行环境及开发软件: 1.4 可行性的分析 1.4.1 技术可行性 1.4.2 经济可行性 1.4.3 操作可行性 2、 需求分析 2.1 用户需求分析 2.2功能需求分析 2.3界面设计需求分析…...
智慧工地信息管理与智能预警平台
建设背景与政策导向 智慧工地信息管理与智能预警平台的出现,源于工地管理面临的诸多挑战,如施工地点分散、危险区域多、监控手段落后等。随着政府对建筑产业现代化的积极推动,各地纷纷出台政策支持智慧工地的发展,旨在通过信息技…...
使用Apache Mahout制作 推荐引擎
目录 创建工程 基本概念 关键概念 基于用户与基于项目的分析 计算相似度的方法 协同过滤 基于内容的过滤 混合方法 创建一个推荐引擎 图书评分数据集 加载数据 从文件加载数据 从数据库加载数据 内存数据库 协同过滤 基于用户的过滤 基于项目的过滤 添加自定…...
记录:导出功能:接收文件流数据进行导出(vue3)
请求接口:一定要加responseType: blob 后端返回数据: api.js export function export() {return request({url: dev/api/export,method: get,responseType: blob,//一定要加}) } vue: import {export} from /api// 导出 const exportTab…...
GraphRAG vs 传统 RAG:如何通过知识图谱提升 AI 检索能力
相比传统 RAG 仅能独立检索文本片段的局限性,GraphRAG通过构建实体关系图谱实现了信息间的连接,让 AI 能更完整地理解和检索复杂的关联信息,从而生成更准确和连贯的回答 问题背景: 想象有一本详细记录某人(X)成就的传记,每个章节都描述了他的…...
问题清除指南|关于num_classes与 BCELoss、BCEWithLogitsLoss 和 CrossEntropyLoss 的关系
前言:关于「 num_classes 1 」引发的探究。 2024年尾声,学弟问到一个问题:在研究工作 CNNDetection 的github开源代码 networks/trainer.py 文件的 line 27 self.model resnet50(num_classes1) 中,变量 num_classes 的值为1&…...
组网实训实现
小型单元网络实现 IP划分: 外网:172.1.1.0/24 172.1.2.0/24 内网:基于192.168.3.0/24的子网划分 综合办公楼:192.168.3.00 000000 /26(192.168.3.0-192.168.3.63) 综合一楼:192.168.3.0000 0000 /28&…...
【DevOps】Jenkins部署
Jenkins部署 文章目录 Jenkins部署资源列表基础环境一、部署Gilab1.1、安装Gitlab1.2、修改配置文件1.3、加载配置文件1.4、访问Gitlab1.5、修改root登录密码1.6、创建demo测试项目1.7、上传代码1.8、验证上传的代码 二、部署Jenkins所需软件2.1、部署JDK2.2、部署Tomcat2.3、部…...
HTML——38.Span标签和字符实体
<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>span标签和字符实体</title><style type"text/css">h1{text-align: center;}p{text-indent: 2em;}span{color: red;}</style></head><…...
doris:基于 Arrow Flight SQL 的高速数据传输链路
Doris 基于 Arrow Flight SQL 协议实现了高速数据链路,支持多种语言使用 SQL 从 Doris 高速读取大批量数据。 用途 从 Doris 加载大批量数据到其他组件,如 Python/Java/Spark/Flink,可以使用基于 Arrow Flight SQL 的 ADBC/JDBC 替代过去…...
文献阅读 | B. S. Carmo 2010
目录 一、文献名称二、原文地址三、ABSTRACT主要发现详细观察分岔分析雷诺数依赖性比较见解意义结论 四、IINTRODUCTION历史研究回顾计算研究近期研究进展研究空白与目的论文结构 一、文献名称 二、原文地址 研究目的:研究串列排列双圆柱体周围流场中的次级不稳定性…...
GRAPE——RLAIF微调VLA模型:通过偏好对齐提升机器人策略的泛化能力(含24年具身模型汇总)
前言 24年具身前沿模型大汇总 过去的这两年,工作之余,我狂写大模型与具身的文章,加之具身大火,每周都有各种朋友通过CSDN私我及我司「七月在线」寻求帮助/指导(当然,也欢迎各大开发团队与我司合作共同交付)…...
超越YOLO11!DEIM:先进的实时DETR目标检测
DEIM: DETR with Improved Matching for Fast Convergence arXiv: https://arxiv.org/abs/2412.04234 Project webpage:https://www.shihuahuang.cn/DEIM/ GitHub:https://github.com/ShihuaHuang95/DEIM 1 背景:DETR目标检测框架 目标检…...
django vue3实现大文件分段续传(断点续传)
前端环境准备及目录结构: npm create vue 并取名为big-file-upload-fontend 通过 npm i 安装以下内容"dependencies": {"axios": "^1.7.9","element-plus": "^2.9.1","js-sha256": "^0.11.0&quo…...
用户注册模块(芒果头条项目进度4)
1 创建⽤户模块⼦应⽤ 1.1 在项⽬包⽬录下 创建apps的python包。 1.2 在apps包下 创建应⽤userapp $ cd 项⽬包⽬录/apps $ python ../../manage.py startapp userapp 1.3 配置导包路径 默认情况下导包路径指向项⽬根⽬录 # 通过下⾯语句可以打印当前导包路径 print(sys.pa…...
Java Map集合、集合的嵌套
一. 概述 1. Map集合称为双列集合,格式:{key1value1, key2value2,.....},一次需要存一对数据作为一个元素。 2. Map集合的每个元素"keyvalue"称为一个键值对/键值对对象/一个Entry对象,Map集合也被称为"键值对集合"。 3.…...
C#中使用系统默认应用程序打开文件
有时您可能希望程序使用默认应用程序打开文件。 例如,您可能希望显示 PDF 文件、网页或互联网上的 URL。 System.Diagnostics.Process类的Start方法启动系统与文件关联的应用程序。 例如,如果文件扩展名为.txt,则系统会在 NotePad、WordPa…...
论文泛读《LPFHE: Low-Complexity Polynomial CNNs for Secure Inference over FHE》
文章目录 1、摘要2、介绍3、文章结构4、总结 1、摘要 Machine learning as a service (MLaaS) 在客户中越来越受欢迎。为了解决 MLaaS 中的隐私问题,引入了 FHE 来保护客户端的数据。 然而,FHE 不能直接评估 卷积神经网络 (CNNs) 中的非算数激活函数。…...
基于Spring Boot的IT技术交流和分享平台的设计与实现源码
风定落花生,歌声逐流水,大家好我是风歌,混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的IT技术交流和分享平台的设计与实现。项目源码以及部署相关请联系风歌,文末附上联系信息 。 项目简介: 基于S…...
力扣hot100——二分查找
35. 搜索插入位置 class Solution { public:int searchInsert(vector<int>& a, int x) {if (a[0] > x) return 0;int l 0, r a.size() - 1;while (l < r) {int mid (l r 1) / 2;if (a[mid] < x) l mid;else r mid - 1;}if (a[l] x) return l;else …...
1月第一讲:WxPython跨平台开发框架之前后端结合实现附件信息的上传及管理
1、功能描述和界面 前端(wxPython GUI): 提供文件选择、显示文件列表的界面。支持上传、删除和下载附件。展示上传状态和附件信息(如文件名、大小、上传时间)。后端(REST API 服务):…...