Nest的test中的best是Jest框架

news2024/11/18 11:34:13

Nest的test中的best是Jest框架

前言

花了3天时间给自己之前做的一个小系统基本补完了单元测试,趁此机会>脑袋里对于单元测试的知识还算热乎,来输出一篇比较详细的关于单元测试的文章,以梳理知识,融汇贯通;如果对你有所帮助,当然最好不过🎉

标题是不是挺有趣的😀😀😀,不过best可能有待商榷,毕竟最高级这种单词还是不能随便乱用,下面先看运行截图:

嗯^~^,舒服了...

虽然还有一点小瑕疵,但是不伤大雅,下面细细道来...

单元测试的好处

什么是单元测试

首先谈谈单元测试的定义,它是什么,了解它才能知道它的优点有哪些塞:单元测试是一种软件测试方法,它对软件系统中的最小可测试单元进行测试,通常是函数或者方法。

来,我们一起来理解一下这个定义:

  • 好比我们小学做数学题,每做完一题之后是不是需要验算一次,才能放心地做下一道题,这就是单元测试,以一道题目作为一个单元进行测试;
  • 而当我们做完整张试卷的时候,通常也要花20分钟去对整张卷子再检查一遍,甚至不止一遍,才能放心交卷。
  • 虽然最后一次的全部检查一次虽然很有必要,毕竟是要交付了嘛;但是很多时候也是在进行重复性的工作,至少笔者以前做试卷是这样的:把每一次的验算方法再做一遍,才能心安...

试卷中当然只能手算,但程序中却可以有很大的优化,这也是计算机的优势之一——自动化。

这得益于"算法"中的一个性质-确定性(Definiteness),这意味着我们编写了一个函数,我们可以清晰的知道这个函数每个步骤运行之后最终的结果,也就是同一个输入用例,程序的输出预期都是一样。

我们学C语言课程的时候,是不是要写一个helloWorld()函数,写完之后我们是不是要运行才能知道我们写得对不对是吧。而一个软件系统就是由非常多的“helloWorld()”这种函数单元组成的。

  • 此时我们就不可能再手动地去console.log(helloWorld(case1))之类的重复性工作,就需要自动化的单元测试了,以方便我们再每一次交付的时候,非常放心地知道之前做的每一道题都没有问题;
  • 甚至说很多题目都是有关联的,比如第一小题错了,整道大题都得不了分了。软件系统中的每一个程序之间的关联依赖更为复杂,很多时候即使99%的代码单元都没问题,那1%的错误代码也可能导致整个系统崩溃。

这里其实也简单谈了谈单元测试的必要性了,下面就详细谈谈单元测试的好处

提高代码质量

不经过测试的代码,你自己能放心吗?答案是否定的。

在编写单元测试的时候,会给我们再一次思考该函数单元的逻辑的机会。如果按照先写代码,再写测试用例的流程来说:

  • 在写函数单元代码的时候,我们可能更多的是作为一个局里人;而在写测试的时候,我们就成为了一个旁观者,在阅读、思考、评判“另一个自己”写的代码。
  • 就好比生成式对抗网络(GAN)的基本思想——对抗,它的主要结构包括一个生成器和一个判别器。此时“写代码的自己”就是生成器,而“写测试的自己”就是判别器。效果自然非常不错。

“左脚踩右脚”还真能上天

提高代码可维护性

这一点主要得益于“自动化”这个特性,它不需要我们重复为以前的代码编写测试用例,一次编写基本可以在后续一直使用。

具体来说:

  • 一个软件系统通常是非常复杂的,代码之间的关联依赖可能非常高,在不对所有函数单元进行测试的前提下,你很难放心你的新增代码是否有影响到其他的部分,让其它的函数单元不能正常运行。
  • 特别是作为螺丝钉的打工人,对整个软件系统还不是很熟悉的时候,很难保证自己新增的改动是否有影响到其他部分,这时候单元测试给与我们的PASS是非常让人放心的。
提高代码可读性及团队协作

一句经典的话“代码是写给人看的,给机器的是二进制”。

单元测试是从另一个角度描述了对应代码的逻辑,并且其中describe()it()通常也包含非常可读的注释(这里笔者习惯it这种风格,但道理都是一样的),比如:


describe('helloWorld', () => {

it('should return "Hello World!"', () => {

expect(appResolver.helloWorld()).toBe('Hello World!');

});

});

这样我们是不是就知道了这个函数是返回Helli World字符串的作用的。当函数单元的逻辑比较复杂时,也通常会对应着非常多的测试用例,通过阅读这些测试用例,我们就可以非常清楚的了解到该函数单元的设计者当时是怎么思考这个函数对应的功能(什么样的输入对应什么样的输出)

团队协作中非常重要的一点就是沟通,这是不是跟注释和技术文档的作用一样,提高了沟通效率,以及当团队换人的时候,新人也能快速上手🙃🙃🙃

单元测试相关概念

单元测试的任务

首先,单元测试越早越好!

然后,如下是我们编写单元测试需要做的事情:

  1. 模块接口测试:比如函数作为一个模块,它的输入输出就是它的接口;面向对象中,也可以一个类作为一个模块单元,那么此时它的构造函数以及public属性和方法就是所谓的接口。简单来说就是该模块单元与其他模块交互的地方。
  2. 模块局部数据结构测试:保证临时存储在模块内的程序执行过程完整、正确
  3. 模块边界条件测试:采用边界值分析技术
  4. 模块中所有独立执行通路测试:保证模块中每条语句至少执行一次
  5. 模块中的各条错误处理通路测试
单元测试覆盖率

单元测试覆盖率(Unit Test Coverage)是一种衡量软件测试质量的指标,它表示测试用例覆盖代码中的哪些部分。以下是单元测试覆盖率的相关概念:

  • 语句覆盖率(Statement Coverage):语句覆盖率是指测试用例执行时覆盖了代码中的哪些语句。如果一个语句被至少一个测试用例执行过,那么它就被认为是被覆盖的。语句覆盖率越高,表示测试用例覆盖的代码越多,测试质量越高。
  • 分支覆盖率(Branch Coverage):分支覆盖率是指测试用例执行时覆盖了代码中的哪些分支。如果一个分支被至少一个测试用例执行过,该分支的取真和取假条件都执行过,那么它就被认为是被覆盖的。分支覆盖率越高,表示测试用例覆盖的代码分支越多,测试质量越高。
  • 条件覆盖率(Condition Coverage):条件覆盖率是指测试用例执行时覆盖了代码中的哪些条件。如果一个条件被至少一个测试用例执行过,并且覆盖了该条件的所有可能取值,那么它就被认为是被覆盖的。条件覆盖率越高,表示测试用例覆盖的代码条件越多,测试质量越高。
  • 路径覆盖率(Path Coverage):路径覆盖率是指测试用例执行时覆盖了代码中的哪些路径。路径是指代码执行的所有可能序列。如果一个路径被至少一个测试用例执行过,那么它就被认为是被覆盖的。路径覆盖率越高,表示测试用例覆盖的代码路径越多,测试质量越高。

第一点就不谈了,二三四可能大家还有一点混淆,这里简单解释一下:

比如我们的函数单元中有这样两个分支条件:


function fn(A, B, C, D){

if(A && B) {

console.log('A && B...')

}

if(C || D) {

console.log('C || D...')

}

}

画个流程图,更加清晰的观察这个函数单元的逻辑,如下:


1)分支覆盖

使其分支覆盖的测试用例可以是:

  • 测试用例1:A = true; B = true; C = true; D = false,此时A && B = true; C || D = true即所有分支都全部执行;
  • 测试用例2:A = false; B = true; C = false; D = false,此时A && B = false; C || D = false即所有分支都没执行;

这样,每个分支都有被执行的情况以及不被执行的情况,此时已经达到了分支覆盖;

2)条件覆盖

  • 分支覆盖是使其if(整体条件)中的整体条件取真一次,取假一次就可以了,即以分支为一个粒度;
  • 而条件覆盖是使其if(条件1、条件2...)中的所有条件都要取真取假一次,即以分支中每个单独条件为一个粒度;

使其条件覆盖的测试用例可以是:

  • 测试用例1:A = true; B = true; C = true; D = true,即所有条件都为真;
  • 测试用例2:A = false; B = false; C = false; D = false,即所有条件都为假;

3)路径覆盖

上述两种覆盖方式没有如下执行情况:

  • 分支1执行,分支2不执行;
  • 分支1不执行,分支1执行;

即一个达到路径覆盖的测试用例应该包含如下情况:

分支1分支2
执行执行
执行不执行
不执行执行
不执行不执行

笔者认为,这些虽然是规范,指标,但仅仅作为参考和流程化使用。好的单测用例应该是只要这个函数单元对应的测试用例只要执行通过了,那么这个函数就一定没有任何问题,这才是终极目标。

Nest中配置Jest

你可以先看看这里Nest官方文档-单元测试

然后你可以在package.json里面进行一些单测的自定义配置,如下是笔者的单测配置:


{

// 略...

"jest": {

"moduleFileExtensions": [

"js",

"json",

"ts"

],

"rootDir": ".",

"moduleNameMapper": {

"^src/(.*)$": "<rootDir>/src/$1"

},

"testRegex": ".*\\.spec\\.ts$",

"transform": {

"^.+\\.(t|j)s$": "ts-jest"

},

"collectCoverageFrom": [

"**/*.service.ts",

"**/utils/*.ts",

"!**/*.interface.ts",

"!**/gql-config.service.ts",

"!**/node_modules/**"

],

"coverageDirectory": "../coverage",

"testEnvironment": "node"

},

}

其中有两点值得提一下:

  1. testRegex:代表哪些为测试文件,当你npm run test或者jest时,会执行这些被识别的测试文件,通常在nest就是XX.spec.ts为测试文件;
  2. collectCoverageFrom:代表单测覆盖率会考虑哪些文件,当你npm run test:cov(自定义脚本)或者jest --coverage时,会考虑识别的文件并形成单元测试覆盖率报告;

这里笔者由于业务逻辑是全部写在Service层里面,关于分层的作用,可以查看笔者之前写的这篇文章——浅谈NestJS设计思想(分层、IOC、AOP)

所以为了一定程度减少工作量,这里笔者仅对Service文件进行考虑单元测试,Controller层以及Resolver层基本都是透传,暂不考虑单测;

然后由于utils/下的文件一般都是工具函数,所以也一定要进行单元测试,而!符号代表不匹配哪些文件,比如.interface.ts后缀的文件是nest中的类型文件,所以不需要在单测覆盖中考虑

最后,请根据自己的实际情况进行配置,这里仅供参考,详细配置项可见Jest官网文档-配置

Jest中Mock Prisma

Prisma是笔者在Nest应用中使用的一个ORM框架,一般来说,在执行单测的时候,不应该执行数据库操作和外部请求的一些操作,原因如下:

  • 单元测试应该专注于测试代码本身,而不是依赖于外部资源或环境。执行数据库操作和外部请求操作会使单元测试变得复杂和不可靠,因为这些操作可能会受到外部环境的影响,例如网络连接、数据库状态等等。
  • 执行数据库操作和外部请求操作还会导致单元测试变得缓慢和不可重复。
    • 如果测试依赖于外部资源,那么测试的速度会变得很慢,因为需要建立连接、读取数据等等。
    • 如果测试依赖于外部资源,那么测试的结果可能会受到外部资源的影响,例如数据库中的数据、网络连接等等,这会导致测试结果不可重复。

再来理解一下,就一句话:对自己编写的业务代码负责就行了

所以这里我们需要mock Prisma的操作以避免对数据库的操作,Prisma的官方推荐是如下配置:

先安装:

npm install jest-mock-extended@2.0.4 --save-dev 

然后我们就可以这样编写单元测试代码:


import { Test, TestingModule } from '@nestjs/testing';

import { UsersService } from './users.service';

import { PrismaClient } from '@prisma/client';

import { mockDeep, DeepMockProxy } from 'jest-mock-extended';

import { UpdateUserInput } from './dto/update-user.input';

import { PrismaService } from 'nestjs-prisma';


describe('UsersService', () => {

let service: UsersService;

let prisma: DeepMockProxy<PrismaClient>;


beforeEach(async () => {

const module: TestingModule = await Test.createTestingModule({

providers: [UsersService, PrismaService],

})

.overrideProvider(PrismaService)

.useValue(mockDeep<PrismaClient>())

.compile();


service = module.get(UsersService);

prisma = module.get(PrismaService);

});


describe('updateUser', () => {

it('return new user', () => {

const userId = '1';

const newUserData: UpdateUserInput = { nickName: 'Justin3go' };


prisma.user.update.mockResolvedValueOnce('a new user entity' as any);


expect(service.updateUser(userId, newUserData)).resolves.toBe(

'a new user entity'

);

});

});

});

Jest编写单测用例

得益于Jest的各个函数命名非常规范,你自己dot一下,根据IDE的提示基本就知道怎么做了,具体看看Jest文档

没啥说的,贴一部分代码仅供参考:

这部分代码的作用是小程序登录注册,以及使用JWT进行授权的Auth.service


// auth.service.ts

import { PrismaService } from 'nestjs-prisma';

import { User } from '@prisma/client';

import { Injectable, UnauthorizedException } from '@nestjs/common';

import { ConfigService } from '@nestjs/config';

import { JwtService } from '@nestjs/jwt';

import { Token } from './models/token.model';

import { SecurityConfig } from 'src/common/configs/config.interface';

import {

code2SessionAPI,

code2SessionParamsI,

code2SessionResI,

code2SessionTimeout,

} from 'src/common/apis/wechat.api';

import { HttpService } from '@nestjs/axios';

import { catchError, lastValueFrom, map, of, timeout } from 'rxjs';

import { LoginAndAutoSignUpInput } from './dto/loginAndAutoSignUp.input';


@Injectable()

export class AuthService {

constructor(

private readonly jwtService: JwtService,

private readonly prisma: PrismaService,

private readonly configService: ConfigService,

private readonly httpService: HttpService

) {}


async loginAndAutoSignUp(data: LoginAndAutoSignUpInput): Promise<Token> {

const { code } = data;

const code2SessionRes: code2SessionResI = await this.code2Session(code);

const { session_key, openid, errmsg } = code2SessionRes ?? {};

if (!session_key || !openid) {

throw new Error(`Wechat login failed: ${errmsg || 'null'}`);

}

// find user

let user = await this.prisma.user.findUnique({

where: { openId: openid },

});

// sign up

if (!user) {

// create user

user = await this.prisma.user.create({

data: {

openId: openid,

},

});

}

return this.generateTokens({

userId: user.id,

});

}


// 小程序登录

async code2Session(code: string): Promise<code2SessionResI> {

const appid = this.configService.get<string>('APP_ID');

if (!appid) {

throw new Error('you should add <APP_ID=XXX> in .env file');

}

const secret = this.configService.get<string>('APP_SECRET');

if (!secret) {

throw new Error('you should add <APP_SECRET=XXX> in .env file');

}

const params: code2SessionParamsI = {

appid,

secret,

js_code: code,

grant_type: 'authorization_code',

};

const res = await lastValueFrom(

this.httpService

.get(code2SessionAPI, {

params,

})

.pipe(

map((res) => res.data),

timeout(code2SessionTimeout),

catchError((error) => of(`Bad HttpService: ${error}`))

)

);

return res;

}


validateUser(userId: string): Promise<User | null> {

return this.prisma.user.findUnique({ where: { id: userId } });

}


getUserFromToken(token: string): Promise<User | null> {

const decodeToken = this.jwtService.decode(token);

const id = decodeToken && decodeToken['userId'];

return this.prisma.user.findUnique({ where: { id } });

}


generateTokens(payload: { userId: string }): Token {

return {

accessToken: this.generateAccessToken(payload),

refreshToken: this.generateRefreshToken(payload),

};

}


private generateAccessToken(payload: { userId: string }): string {

return this.jwtService.sign(payload);

}


private generateRefreshToken(payload: { userId: string }): string {

const securityConfig = this.configService.get<SecurityConfig>('security');

return this.jwtService.sign(payload, {

secret: this.configService.get('JWT_REFRESH_SECRET'),

expiresIn: securityConfig?.refreshIn,

});

}


refreshToken(token: string) {

try {

const { userId } = this.jwtService.verify(token, {

secret: this.configService.get('JWT_REFRESH_SECRET'),

});


return this.generateTokens({

userId,

});

} catch (e) {

throw new UnauthorizedException();

}

}

}

单测代码auth.service.spec.ts


import { Test, TestingModule } from '@nestjs/testing';

import { AuthService } from './auth.service';

import { PrismaClient } from '@prisma/client';

import { mockDeep, DeepMockProxy } from 'jest-mock-extended';

import { PrismaService } from 'nestjs-prisma';

import { JwtService } from '@nestjs/jwt';

import { ConfigService } from '@nestjs/config';

import { HttpModule, HttpService } from '@nestjs/axios';

import { Observable } from 'rxjs';

import { UnauthorizedException } from '@nestjs/common';


describe('AuthService', () => {

let service: AuthService;

let prisma: DeepMockProxy<PrismaClient>;

let jwt: JwtService;

let config: ConfigService;

let http: HttpService;


beforeEach(async () => {

// https://stackoverflow.com/questions/63208308/how-to-fix-axios-instance-token-at-index-0-is-available-in-the-module-context

const module: TestingModule = await Test.createTestingModule({

imports: [HttpModule],

providers: [AuthService, PrismaService, JwtService, ConfigService],

})

.overrideProvider(PrismaService)

.useValue(mockDeep<PrismaClient>())

.compile();


service = module.get(AuthService);

prisma = module.get(PrismaService);

jwt = module.get(JwtService);

config = module.get(ConfigService);

http = module.get(HttpService);

});


describe('loginAndAutoSignUp()', () => {

it('Wechat login failed, throw Error', () => {

service.code2Session = jest.fn().mockReturnValue({});

expect(service.loginAndAutoSignUp({ code: '<code>' })).rejects.toThrow();

});


it('sign up, should execute prisma.user.create()', async () => {

service.code2Session = jest.fn().mockReturnValue({

session_key: '<session_key>',

openid: '<openid>',

});

service.generateTokens = jest.fn().mockReturnValue('<generateTokens>');

prisma.user.findUnique.mockResolvedValueOnce(null as any);

prisma.user.create.mockResolvedValueOnce({ id: '<id>' } as any);

await service.loginAndAutoSignUp({ code: '<code>' });

expect(prisma.user.create).toHaveBeenCalled();

});


it('login, correct returns', async () => {

service.code2Session = jest.fn().mockReturnValue({

session_key: '<session_key>',

openid: '<openid>',

});

service.generateTokens = jest.fn().mockReturnValue('<generateTokens>');

prisma.user.findUnique.mockResolvedValueOnce({ id: '<id>' } as any);

expect(service.loginAndAutoSignUp({ code: '<code>' })).resolves.toBe(

'<generateTokens>'

);

});

});


describe('code2Session()', () => {

it('no appid, throw Error', () => {

config.get = jest.fn().mockImplementation((key: string) => {

if (key === 'APP_ID') {

return null;

} else if (key === 'APP_SECRET') {

return '<APP_SECRET>';

}

});

expect(service.code2Session('<code>')).rejects.toThrow();

});

it('no app secret, throw Error', () => {

config.get = jest.fn().mockImplementation((key: string) => {

if (key === 'APP_ID') {

return '<APP_ID>';

} else if (key === 'APP_SECRET') {

return null;

}

});

expect(service.code2Session('<code>')).rejects.toThrow();

});

it('should return res', () => {

config.get = jest.fn().mockReturnValue('<env>');

const observable = new Observable((observer) => {

observer.next('final res');

observer.complete();

});

http.get = jest.fn().mockReturnValue({

pipe: jest.fn().mockReturnValue(observable),

});

expect(service.code2Session('<code>')).resolves.toBe('final res');

});

});


describe('validateUser()', () => {

it('should returns', () => {

prisma.user.findUnique.mockResolvedValueOnce('user' as any);

expect(service.validateUser('userId')).resolves.toBe('user');

});

});


describe('getUserFromToken()', () => {

it('jwt decode null, not throw', () => {

jwt.decode = jest.fn().mockReturnValue(null);

prisma.user.findUnique.mockResolvedValueOnce('user' as any);

expect(service.getUserFromToken('<token>')).resolves.not.toThrow();

});

});


describe('generateTokens()', () => {

it('should returns Object', () => {

config.get = jest.fn().mockReturnValue('<env>');

jwt.sign = jest

.fn()

.mockReturnValueOnce('<accessToken>')

.mockReturnValueOnce('<refreshToken>');

expect(service.generateTokens({ userId: '<userId>' })).toEqual({

accessToken: '<accessToken>',

refreshToken: '<refreshToken>',

});

});

});


describe('refreshToken()', () => {

it('verify fail, throw Error', () => {

jwt.verify = jest.fn().mockReturnValue(null);

config.get = jest.fn().mockReturnValue('<env>');

service.generateTokens = jest.fn().mockReturnValue('<token>');

try {

service.refreshToken('<token>');

} catch (err) {

expect(err).toEqual(new UnauthorizedException());

}

});


it('verify success, returns token', () => {

jwt.verify = jest.fn().mockReturnValue({ userId: '<userId>' });

config.get = jest.fn().mockReturnValue('<env>');

service.generateTokens = jest.fn().mockReturnValue('<token>');


expect(service.refreshToken('<token>')).toBe('<token>');

});

});

});

这里是用的describe it风格,仅供参考学习,如有错误,欢迎友善指正,我会及时修改...

VSCode安装Jest Runner插件,运行如上单元测试截图如下:

CI中增加单测

为了在提交代码后在Github自动执行单元测试,以达到持续集成中的自动化测试的目的,笔者增加了如下配置:

根目录下/.github/workflows/ci.yml


name: Node.js CI


on:

push:

branches: [main]

pull_request:

branches: [main]


jobs:

build:

runs-on: ubuntu-latest


strategy:

matrix:

node-version: [14, 16]


steps:

- uses: actions/checkout@v2

- name: Use Node.js ${{ matrix.node-version }}

uses: actions/setup-node@v2

with:

node-version: ${{ matrix.node-version }}

cache: 'npm'

- run: npm install

- run: npm run lint

- run: npm run build

- run: npm test

当然,实际情况不同也需自行修改

行动吧,在路上总比一直观望的要好,未来的你肯定会感 谢现在拼搏的自己!如果想学习提升找不到资料,没人答疑解惑时,请及时加入扣群: 320231853,里面有各种软件测试+开发资料和技术可以一起交流学习哦。

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1683834.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

配置旁挂二层组网直接转发示例(命令行)

业务需求 企业用户通过WLAN接入网络&#xff0c;以满足移动办公的最基本需求。且在覆盖区域内移动发生漫游时&#xff0c;不影响用户的业务使用。 组网需求 AC组网方式&#xff1a;旁挂二层组网。DHCP部署方式&#xff1a; AC作为DHCP服务器为AP分配IP地址。汇聚交换机SwitchB作…

vue小记——小组件(1)

代码&#xff1a; <template><div><el-steps :active"active" finish-status"success" simple><el-step title"数据导入"><i class"fa fa-cloud-upload fa-icon-custom" slot"icon"></i…

Docker搭建mysql性能测试环境

OpenEuler使用Docker搭建mysql性能测试环境 一、安装Docker二、docker安装mysql三、测试mysql连接 一、安装Docker 建立源文件vim /etc/yum.repos.d/docker-ce.repo增加内容[docker-ce-stable] nameDocker CE Stable - $basearch baseurlhttps://repo.huaweicloud.com/docker…

【手势识别-UIPinchGestureRecognizer捏合-UIPanGestureRecognizer缩放 Objective-C语言】

一、接下来,我们来说这个捏合,和,这个缩放啊 1.捏合, 首先呢,步骤,也都是一样的啊, 1)创建手势对象 2)添加手势 3)实现手势方法 pinch:捏合 UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:(id) action:(SEL)]; U…

ThreadLocal原理及使用

一、引言 在Java多线程编程中&#xff0c;ThreadLocal是一个非常有用的工具&#xff0c;它提供了一种将对象与线程关联起来的机制&#xff0c;使得每个线程都可以拥有自己独立的对象副本&#xff0c;从而避免了线程安全问题。然而&#xff0c;使用不当会导致内存泄漏问题。 二…

spring-boot整合Micrometer+Prometheus

环境&#xff1a; micrometer 1.8.2 prometheus 0.14.1 spring-boot-actuator 2.6.6 使用案例 <!-- Springboot启动actuator&#xff0c;默认会引入依赖&#xff1a;micrometer-core --> <dependency><groupId>org.springframework.boot</groupId>&l…

ctfhub中的SSRF相关例题(中)

目录 上传文件 gopher协议的工作原理&#xff1a; gopher协议的使用方法&#xff1a; 相关例题: FastCGI协议 FastCGI协议知识点 相关例题&#xff1a; Redis协议 知识点&#xff1a; 相关例题 第一种方法 第二种方法 上传文件 gopher协议的工作原理&#xff1a; …

《ESP8266通信指南》番外-(附完整代码)ESP8266获取DHT11接入(基于Lua)

前言 此篇为番外篇,是 ESP8266 入门的其他功能教程,包括但不限于 DHT11 驱动TCP 通信Thingsboard 平台的接入阿里云物联网云平台接入华为云平台接入 1. 小节目标 使用 Lua 驱动 DHT11 传感器,获取温湿度的值 2. 进入主题 NodeMCU 基于 LUA 相关资料 官方文档&#xff1a;…

商品指数创年内新高,粘性通胀成为美联储噩梦

文章概述 虽然美国4月CPI增幅放缓让美联储今年降息的可能性大增&#xff0c;但与此同时&#xff0c;大宗商品价格却达到了一年来的最高水平&#xff0c;粘性通胀可能成为美联储的噩梦。数据显示&#xff0c;跟踪24种能源、金属和农业合约彭博大宗商品现货指数今年以来已经上涨…

Mysql超详细安装配置教程(保姆级图文)

MySQL是一种流行的开源关系型数据库管理系统&#xff0c;它广泛用于网站和服务的数据存储和管理。MySQL以其高性能、可靠性和易用性而闻名&#xff0c;是许多Web应用程序的首选数据库解决方案之一。 一、下载安装包 &#xff08;1&#xff09;从网盘下载安装文件 点击此处直…

RK3588 Android13 TvSetting 中增加字体样式切换功能

前言 电视产品,客户需求又升级了,有了切换字体大小还不行,还得增加动态切换字体样式功能, 同样需要在设备偏好设置子菜单里的显示和声音二级菜单里增加字体样式菜单功能,开整。 效果图 framework 部分修改文件清单 frameworks/base/data/fonts/fonts.mk frameworks/bas…

附代码:策略常用-正余弦优化算法

正余弦优化算法作为群智能优化算法的一种, 正弦余弦算法 (sine cosine algorithm, SCA) 是 2016 年由 Mirjalili 提出的一种新型仿自然优化算法, 通过创建多个随机候选解, 利用正余弦函数的数学性质来平衡算法在搜系过程中的全局探索和局部开发能力。该算法具有结构简单、参数少…

MobaXterm:Network error: Connection refused

问题描述 使用MobaXterm连接服务器或者虚拟机里面的操作系统显示“Network error: Connection refused” 因为服务器或者虚拟机里面的操作系统没安装 ssh 解决方法 安装ssh sudo apt-get update sudo apt-get upgrade sudo apt-get install ssh重启 ssh service ssh resta…

Docker 镜像是什么?

Docker 镜像是什么&#xff1f; Docker 镜像&#xff08;Docker Image&#xff09;是用于创建 Docker 容器的只读模板。它包含了运行应用程序所需的所有内容&#xff0c;包括代码、运行时环境、库、环境变量以及配置文件。Docker 镜像是构建和分发应用程序的基础。 在深入阅读…

[数据集][目标检测]弹簧上料检测数据集VOC+YOLO格式142张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;142 标注数量(xml文件个数)&#xff1a;142 标注数量(txt文件个数)&#xff1a;142 标注类别…

如何保护好源代码

在信息技术飞速发展的今天&#xff0c;源代码作为软件开发的核心要素&#xff0c;其安全性与保密性至关重要。一旦源代码泄露或被恶意篡改&#xff0c;将可能导致企业面临重大损失&#xff0c;甚至威胁到整个行业的安全。因此&#xff0c;如何保护源代码已成为软件企业和个人开…

15.1使用curl命令,命令行模拟登陆discuz

使用curl命令,命令行模拟登陆discuz web保存session&#xff0c;鼠标点一点&#xff0c;发起http请求&#xff0c;html 注意不能使用登录带验证码的网站测试 1.curl命令模拟访问discuz论坛 在192.168.111.16服务器的web站点新建一个目录&#xff0c;获取cookie信息与html文件…

IP学习——ospf1

OSPF:开放式最短路径优先协议 无类别IGP协议&#xff1a;链路状态型。基于 LSA收敛&#xff0c;故更新量较大&#xff0c;为在中大型网络正常工作&#xff0c;需要进行结构化的部署---区域划分、ip地址规划 支持等开销负载均衡 组播更新 ---224.0.0.5 224.0.0.6 …

一剪梅-答赠云安客刘自果

当众网友看了笔者“边吸氧边动鼠标”的短视频之后&#xff0c;纷纷发来微信问候。其中我的远房亲戚&#xff0c;那个正在潜心写作数十万字的长篇纪实文学《川江向东流》的66岁贤弟刘自果&#xff08;号云安客&#xff0c;亦称自果居士&#xff09;&#xff0c;发来微信鼓励我&a…

mysql 多表关联查询性能优化-同一sql不同的执行计划

一、问题背景 相同的sql&#xff0c;不同的日期&#xff0c;执行的时间差异很大&#xff0c;执行计划不一样。执行快时&#xff0c;30ms左右。执行慢时&#xff0c;15s左右。 二、分析结论 1、经过分析&#xff0c;发现不同日期下&#xff0c;sql的执行计划不同&#xff0c;驱…