当前位置: 首页 > 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;需要注意防范。本周&#…...

测试微信模版消息推送

进入“开发接口管理”--“公众平台测试账号”&#xff0c;无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息&#xff1a; 关注测试号&#xff1a;扫二维码关注测试号。 发送模版消息&#xff1a; import requests da…...

Linux云原生安全:零信任架构与机密计算

Linux云原生安全&#xff1a;零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言&#xff1a;云原生安全的范式革命 随着云原生技术的普及&#xff0c;安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测&#xff0c;到2025年&#xff0c;零信任架构将成为超…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

C++八股 —— 单例模式

文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全&#xff08;Thread Safety&#xff09; 线程安全是指在多线程环境下&#xff0c;某个函数、类或代码片段能够被多个线程同时调用时&#xff0c;仍能保证数据的一致性和逻辑的正确性&#xf…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

10-Oracle 23 ai Vector Search 概述和参数

一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI&#xff0c;使用客户端或是内部自己搭建集成大模型的终端&#xff0c;加速与大型语言模型&#xff08;LLM&#xff09;的结合&#xff0c;同时使用检索增强生成&#xff08;Retrieval Augmented Generation &#…...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...

GitFlow 工作模式(详解)

今天再学项目的过程中遇到使用gitflow模式管理代码&#xff0c;因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存&#xff0c;无论是github还是gittee&#xff0c;都是一种基于git去保存代码的形式&#xff0c;这样保存代码…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

c# 局部函数 定义、功能与示例

C# 局部函数&#xff1a;定义、功能与示例 1. 定义与功能 局部函数&#xff08;Local Function&#xff09;是嵌套在另一个方法内部的私有方法&#xff0c;仅在包含它的方法内可见。 • 作用&#xff1a;封装仅用于当前方法的逻辑&#xff0c;避免污染类作用域&#xff0c;提升…...