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

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. 函数的执行逻辑&#xff0c;对于给定的输入&#xff0c;输出是否符合预期。 2. 用户行为的响应逻辑。 - 对于单元测试而言&#xff0c;测试粒度较细&#xff0c;需要测试内部状态的变更与相应函数是否成功被调用。 - 对于集成测试而言&a…...

Statistic for ML

statistical concept 統計學概念 免費完整內容 PMF and CDF PMF定義的值是P(Xx)&#xff0c;而CDF定義的值是P(X < x)&#xff0c;x為所有的實數線上的點。 probability mass function (PMF) 概率質量函數 p X ( x ) P ( X x ) pX(x)P(Xx) pX(x)P(Xx) 是離散隨機變數…...

Django 中数据库迁移命令

在 Django 中&#xff0c;python manage.py makemigrations、python manage.py sqlmigrate polls 0003 和 python manage.py migrate 是与数据库迁移相关的重要命令。它们的作用和对应内容如下&#xff1a; 1. python manage.py makemigrations 功能: 此命令会根据你的模型文…...

【机器学习】 卷积神经网络 (CNN)

文章目录 1. 为什么需要 CNN2. CNN 的架构3. 卷积层4. 池化层5. CNN 的应用 1. 为什么需要 CNN 前提&#xff1a;利用前置知识&#xff0c;去掉全连接神经网络中的部分参数&#xff0c;提升学习效率。本质&#xff1a;在 DNN 之前加上 CNN&#xff0c;先去除不必要的参数&…...

Linux中操作中的无痕命令history技巧

当我们需要查看Linux下的操作记录时&#xff0c;就可以用history命令来查看历史记录 1、关闭history记录功能&#xff0c;如果不想让别人看到自己在Linux上的操作命令&#xff0c;可以用这个命令 set o history 2、打开history记录功能 set -o history3、清空记录 histor…...

在CE自动汇编里调用lua函数

CE自动汇编模板里有一个是调用lua函数&#xff0c;但是关于如何使用的资料很少&#xff0c;结果问AI也是各种错误回答&#xff0c;还各种误导... 下面是32位游戏的例子&#xff1a; loadlibrary(luaclient-i386.dll) luacall(openLuaServer(CELUASERVER))CELUA_ServerName: d…...

如何在没有 iCloud 的情况下将联系人从 iPhone 传输到 iPhone

概括 近期iOS 13.5的更新以及苹果公司发布的iPhone SE在众多iOS用户中引起了不小的轰动。此外&#xff0c;不少变化&#xff0c;如暴露通知 API、Face ID 增强功能以​​及其他在 COVID-19 期间与公共卫生相关的新功能&#xff0c;吸引了 iPhone 用户尝试新 iPhone 并更新到最…...

欧科云链研究院:ChatGPT 眼中的 Web3

编辑&#xff5c;OKG Research 转眼间&#xff0c;2024年已经进入尾声&#xff0c;Web3 行业经历了热闹非凡的一年。今年注定也是属于AI的重要一年&#xff0c;OKG Research 决定拉上 ChatGPT 这位“最懂归纳的AI拍档”&#xff0c;尝试把一整年的研究内容浓缩成精华。我们一共…...

行为模式2.命令模式------灯的开关

行为型模式 模板方法模式&#xff08;Template Method Pattern&#xff09;命令模式&#xff08;Command Pattern&#xff09;迭代器模式&#xff08;Iterator Pattern&#xff09;观察者模式&#xff08;Observer Pattern&#xff09;中介者模式&#xff08;Mediator Pattern…...

Kerberos用户认证-数据安全-简单了解-230403

hadoop安全模式官方文档&#xff1a;https://hadoop.apache.org/docs/r2.7.2/hadoop-project-dist/hadoop-common/SecureMode.html kerberos是什么 kerberos是计算机网络认证协议&#xff0c;用来在非安全网络中&#xff0c;对个人通信以安全的手段进行身份认证。 概念&#…...

【Multisim用74ls92和90做六十进制】2022-6-12

缘由Multisim如何用74ls92和90做六十进制-其他-CSDN问答 74LS92、74LS90参考...

滴滴工作流引擎Turbo与logicFlow研究

目录 logicFlow turbo 工作流引擎很多,也都提供了前端UI库,但是太过于冗杂了,元数据表都几十个,logincFlow和Turbo的组合提供了轻量化方式,turbo后端代码只有5个元数据表,logicFlow也提供了bpm的相关扩展功能,但缺点是turbo社区不活跃,logicFlow个人认为跟echarts这种…...

AE Pinnacle 10x6 kW DeviceNet MDXL User r Manual

AE Pinnacle 10x6 kW DeviceNet MDXL User r Manual...

Flutter Android修改应用名称、应用图片、应用启动画面

修改应用名称 打开Android Studio&#xff0c;打开对应项目的android文件。 选择app下面的manifests->AndroidManifest.xml文件&#xff0c;将android:label"bluetoothdemo2"中的bluetoothdemo2改成自己想要的名称。重新启动或者重新打包&#xff0c;应用的名称…...

Nginx rewrite 执行顺序(草稿,下次继续编辑)

个人结论: 1.server层ngx_http_rewrite_module模块相关指令按照配置顺序依次执行&#xff1b; 2.server层执行完break指令后&#xff0c;该层级所有跟ngx_http_rewrite_module模块相关的指令都不再被执行&#xff0c;但是不影响其他模块(例如:https://zhuanlan.zhihu.com/p/357…...

01.03周五F34-Day44打卡

文章目录 1. 这家医院的大夫和护士对病人都很耐心。2. 她正跟一位戴金边眼镜的男士说话。3. 那个人是个圆脸。4. 那个就是传说中的鬼屋。5. 他是个很好共事的人。6. 我需要一杯提神的咖啡。7. 把那个卷尺递给我一下。 ( “卷尺” 很复杂吗?)8. 他收到了她将乘飞机来的消息。9.…...

数字货币支付系统开发搭建:构建未来的区块链支付生态

随着数字货币的迅猛发展&#xff0c;越来越多的企业和机构开始关注如何搭建一个高效、安全、可扩展的数字货币支付系统。区块链技术因其去中心化、安全性高、透明性强等优势&#xff0c;已成为开发数字货币支付系统的首选技术。本文将深入探讨数字货币支付系统的开发和搭建过程…...

NLP CH3复习

CH3 3.1 几种损失函数 3.2 激活函数性质 3.3 哪几种激活函数会发生梯度消失 3.4 为什么会梯度消失 3.5 如何解决梯度消失和过拟合 3.6 梯度下降的区别 3.6.1 梯度下降&#xff08;GD&#xff09; 全批量&#xff1a;在每次迭代中使用全部数据来计算损失函数的梯度。计算成本…...

BurpSuite2024.11

新增功能 2024 年 11 月 25 日&#xff0c;版本 24.11 此版本引入了站点地图过滤器 Bambdas、匹配和替换 Bambdas、用于 API 扫描的动态身份验证令牌&#xff0c;以及用于入侵者攻击的增强负载管理。我们还进行了多项用户体验改进、性能改进和一些错误修复。 使用 Bambdas 过…...

亚信安全2025年第1期《勒索家族和勒索事件监控报告》

本周态势快速感知 本周全球共监测到勒索事件51起&#xff0c;本周勒索事件数量降至近一年来最低&#xff0c;需注意防范。从整体上看Ransomhub依旧是影响最严重的勒索家族&#xff1b;本周Acrusmedia和Safepay也是两个活动频繁的恶意家族&#xff0c;需要注意防范。本周&#…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

vscode里如何用git

打开vs终端执行如下&#xff1a; 1 初始化 Git 仓库&#xff08;如果尚未初始化&#xff09; git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

2025季度云服务器排行榜

在全球云服务器市场&#xff0c;各厂商的排名和地位并非一成不变&#xff0c;而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势&#xff0c;对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析&#xff1a; 一、全球“三巨头”…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

手机平板能效生态设计指令EU 2023/1670标准解读

手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读&#xff0c;综合法规核心要求、最新修正及企业合规要点&#xff1a; 一、法规背景与目标 生效与强制时间 发布于2023年8月31日&#xff08;OJ公报&…...

华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)

题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...

Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解

文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一&#xff1a;HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二&#xff1a;Floyd 快慢指针法&#xff08;…...

TCP/IP 网络编程 | 服务端 客户端的封装

设计模式 文章目录 设计模式一、socket.h 接口&#xff08;interface&#xff09;二、socket.cpp 实现&#xff08;implementation&#xff09;三、server.cpp 使用封装&#xff08;main 函数&#xff09;四、client.cpp 使用封装&#xff08;main 函数&#xff09;五、退出方法…...

初探用uniapp写微信小程序遇到的问题及解决(vue3+ts)

零、关于开发思路 (一)拿到工作任务,先理清楚需求 1.逻辑部分 不放过原型里说的每一句话,有疑惑的部分该问产品/测试/之前的开发就问 2.页面部分(含国际化) 整体看过需要开发页面的原型后,分类一下哪些组件/样式可以复用,直接提取出来使用 (时间充分的前提下,不…...

[QMT量化交易小白入门]-六十二、ETF轮动中简单的评分算法如何获取历史年化收益32.7%

本专栏主要是介绍QMT的基础用法,常见函数,写策略的方法,也会分享一些量化交易的思路,大概会写100篇左右。 QMT的相关资料较少,在使用过程中不断的摸索,遇到了一些问题,记录下来和大家一起沟通,共同进步。 文章目录 相关阅读1. 策略概述2. 趋势评分模块3 代码解析4 木头…...