如何为 Nestjs 编写单元测试和 E2E 测试

前言

最近在给一个 nestjs 项目写单元测试(Unit Testing)和 e2e 测试(End-to-End Testing,端到端测试,简称 e2e 测试),这是我第一次给后端项目写测试,发现和之前给前端项目写测试还不太一样,导致在一开始写测试时感觉无从下手。后来在看了一些示例之后才想明白怎么写测试,所以打算写篇文章记录并分享一下,以帮助和我有相同困惑的人。

同时我也写了一个 demo 项目,相关的单元测试、e2e 测试都写好了,有兴趣可以看一下。代码已上传到 Github: nestjs-interview-demo。

单元测试和 E2E 测试的区别

单元测试和 e2e 测试都是软件测试的方法,但它们的目标和范围有所不同。

单元测试是对软件中的最小可测试单元进行检查和验证。比如一个函数、一个方法都可以是一个单元。在单元测试中,你会对这个函数的各种输入给出预期的输出,并验证功能的正确性。单元测试的目标是快速发现函数内部的 bug,并且它们容易编写、快速执行。

而 e2e 测试通常通过模拟真实用户场景的方法来测试整个应用,例如前端通常使用浏览器或无头浏览器来进行测试,后端则是通过模拟对 API 的调用来进行测试。

在 nestjs 项目中,单元测试可能会测试某个服务(service)、某个控制器(controller)的一个方法,例如测试 Users 模块中的 update 方法是否能正确的更新一个用户。而一个 e2e 测试可能会测试一个完整的用户流程,如创建一个新用户,然后更新他们的密码,然后删除该用户。这涉及了多个服务和控制器。

编写单元测试

为一个工具函数或者不涉及接口的方法编写单元测试,是非常简单的,你只需要考虑各种输入并编写相应的测试代码就可以了。但是一旦涉及到接口,那情况就复杂了。用代码来举例:

async validateUser(
  username: string,
  password: string,
): Promise<UserAccountDto> {
  const entity = await this.usersService.findOne({ username });
  if (!entity) {
    throw new UnauthorizedException('User not found');
  }

  if (entity.lockUntil && entity.lockUntil > Date.now()) {
    const diffInSeconds = Math.round((entity.lockUntil - Date.now()) / 1000);
    let message = `The account is locked. Please try again in ${diffInSeconds} seconds.`;
    if (diffInSeconds > 60) {
      const diffInMinutes = Math.round(diffInSeconds / 60);
      message = `The account is locked. Please try again in ${diffInMinutes} minutes.`;
    }

    throw new UnauthorizedException(message);
  }

  const passwordMatch = bcrypt.compareSync(password, entity.password);
  if (!passwordMatch) {
    // $inc update to increase failedLoginAttempts
    const update = {
      $inc: { failedLoginAttempts: 1 },
    };

    // lock account when the third try is failed
    if (entity.failedLoginAttempts + 1 >= 3) {
      // $set update to lock the account for 5 minutes
      update['$set'] = { lockUntil: Date.now() + 5 * 60 * 1000 };
    }

    await this.usersService.update(entity._id, update);
    throw new UnauthorizedException('Invalid password');
  }

  // if validation is sucessful, then reset failedLoginAttempts and lockUntil
  if (
    entity.failedLoginAttempts > 0 ||
    (entity.lockUntil && entity.lockUntil > Date.now())
  ) {
    await this.usersService.update(entity._id, {
      $set: { failedLoginAttempts: 0, lockUntil: null },
    });
  }

  return { userId: entity._id, username } as UserAccountDto;
}

上面的代码是 auth.service.ts 文件里的一个方法 validateUser,主要用于验证登录时用户输入的账号密码是否正确。它包含的逻辑如下:

  1. 根据 username 查看用户是否存在,如果不存在则抛出 401 异常(也可以是 404 异常)
  2. 查看用户是否被锁定,如果被锁定则抛出 401 异常和相关的提示文字
  3. password 加密后和数据库中的密码进行对比,如果错误则抛出 401 异常(连续三次登录失败会被锁定账户 5 分钟)
  4. 如果登录成功,则将之前登录失败的计数记录进行清空(如果有)并返回用户 idusername 到下一阶段

可以看到 validateUser 方法包含了 4 个处理逻辑,我们需要对这 4 点都编写对应的单元测试代码,以确定整个 validateUser 方法功能是正常的。

第一个测试用例

在开始编写单元测试时,我们会遇到一个问题,findOne 方法需要和数据库进行交互,它要通过 username 查找数据库中是否存在对应的用户。但如果每一个单元测试都得和数据库进行交互,那测试起来会非常麻烦。所以可以通过 mock 假数据来实现这一点。

举例,假如我们已经注册了一个 woai3c 的用户,那么当用户登录时,在 validateUser 方法中能够通过 const entity = await this.usersService.findOne({ username }); 拿到用户数据。所以只要确保这行代码能够返回想要的数据,即使不和数据库交互也是没有问题的。而这一点,我们能通过 mock 数据来实现。现在来看一下 validateUser 方法的相关测试代码:

import { Test } from '@nestjs/testing';
import { AuthService } from '@/modules/auth/auth.service';
import { UsersService } from '@/modules/users/users.service';
import { UnauthorizedException } from '@nestjs/common';
import { TEST_USER_NAME, TEST_USER_PASSWORD } from '@tests/constants';

describe('AuthService', () => {
  let authService: AuthService; // Use the actual AuthService type
  let usersService: Partial<Record<keyof UsersService, jest.Mock>>;

  beforeEach(async () => {
    usersService = {
      findOne: jest.fn(),
    };

    const module = await Test.createTestingModule({
      providers: [
        AuthService,
        {
          provide: UsersService,
          useValue: usersService,
        },
      ],
    }).compile();

    authService = module.get<AuthService>(AuthService);
  });

  describe('validateUser', () => {
    it('should throw an UnauthorizedException if user is not found', async () => {
      await expect(
        authService.validateUser(TEST_USER_NAME, TEST_USER_PASSWORD),
      ).rejects.toThrow(UnauthorizedException);
    });

    // other tests...
  });
});

我们通过调用 usersServicefineOne 方法来拿到用户数据,所以需要在测试代码中 mock usersServicefineOne 方法:

 beforeEach(async () => {
    usersService = {
      findOne: jest.fn(), // 在这里 mock findOne 方法
    };

    const module = await Test.createTestingModule({
      providers: [
        AuthService, // 真实的 AuthService,因为我们要对它的方法进行测试
        {
          provide: UsersService, // 用 mock 的 usersService 代替真实的 usersService 
          useValue: usersService,
        },
      ],
    }).compile();

    authService = module.get<AuthService>(AuthService);
  });

通过使用 jest.fn() 返回一个函数来代替真实的 usersService.findOne()。如果这时调用 usersService.findOne() 将不会有任何返回值,所以第一个单元测试用例就能通过了:

it('should throw an UnauthorizedException if user is not found', async () => {
  await expect(
    authService.validateUser(TEST_USER_NAME, TEST_USER_PASSWORD),
  ).rejects.toThrow(UnauthorizedException);
});

因为在 validateUser 方法中调用 const entity = await this.usersService.findOne({ username });findOne 是 mock 的假函数,没有返回值,所以 validateUser 方法中的第 2-4 行代码就能执行到了:

if (!entity) {
  throw new UnauthorizedException('User not found');
}

抛出 401 错误,符合预期。

第二个测试用例

validateUser 方法中的第二个处理逻辑是判断用户是否锁定,对应的代码如下:

if (entity.lockUntil && entity.lockUntil > Date.now()) {
  const diffInSeconds = Math.round((entity.lockUntil - Date.now()) / 1000);
  let message = `The account is locked. Please try again in ${diffInSeconds} seconds.`;
  if (diffInSeconds > 60) {
    const diffInMinutes = Math.round(diffInSeconds / 60);
    message = `The account is locked. Please try again in ${diffInMinutes} minutes.`;
  }

  throw new UnauthorizedException(message);
}

可以看到如果用户数据里有锁定时间 lockUntil 并且锁定结束时间大于当前时间就可以判断当前账户处于锁定状态。所以需要 mock 一个具有 lockUntil 字段的用户数据:

it('should throw an UnauthorizedException if the account is locked', async () => {
  const lockedUser = {
    _id: TEST_USER_ID,
    username: TEST_USER_NAME,
    password: TEST_USER_PASSWORD,
    lockUntil: Date.now() + 1000 * 60 * 5, // The account is locked for 5 minutes
  };

  usersService.findOne.mockResolvedValueOnce(lockedUser);

  await expect(
    authService.validateUser(TEST_USER_NAME, TEST_USER_PASSWORD),
  ).rejects.toThrow(UnauthorizedException);
});

在上面的测试代码里,先定义了一个对象 lockedUser,这个对象里有我们想要的 lockUntil 字段,然后将它作为 findOne 的返回值,这通过 usersService.findOne.mockResolvedValueOnce(lockedUser); 实现。然后 validateUser 方法执行时,里面的用户数据就是 mock 出来的数据了,从而成功让第二个测试用例通过。

单元测试覆盖率

剩下的两个测试用例就不写了,原理都是一样的。如果剩下的两个测试不写,那么这个 validateUser 方法的单元测试覆盖率会是 50%,如果 4 个测试用例都写完了,那么 validateUser 方法的单元测试覆盖率将达到 100%。

单元测试覆盖率(Code Coverage)是一个度量,用于描述应用程序代码有多少被单元测试覆盖或测试过。它通常表示为百分比,表示在所有可能的代码路径中,有多少被测试用例覆盖。

单元测试覆盖率通常包括以下几种类型:

  • 行覆盖率(Lines):测试覆盖了多少代码行。
  • 函数覆盖率(Funcs):测试覆盖了多少函数或方法。
  • 分支覆盖率(Branch):测试覆盖了多少代码分支(例如,if/else 语句)。
  • 语句覆盖率(Stmts):测试覆盖了多少代码语句。

单元测试覆盖率是衡量单元测试质量的一个重要指标,但并不是唯一的指标。高的覆盖率可以帮助检测代码中的错误,但并不能保证代码的质量。覆盖率低可能意味着有未被测试的代码,可能存在未被发现的错误。

下图是 demo 项目的单元测试覆盖率结果:

在这里插入图片描述

像 service 和 controller 之类的文件,单元测试覆盖率一般尽量高点比较好,而像 module 这种文件就没有必要写单元测试了,也没法写,没有意义。上面的图片表示的是整个单元测试覆盖率的总体指标,如果你想查看某个函数的测试覆盖率,可以打开项目根目录下的 coverage/lcov-report/index.html 文件进行查看。例如我想查看 validateUser 方法具体的测试情况:

在这里插入图片描述

可以看到原来 validateUser 方法的单元测试覆盖率并不是 100%,还是有两行代码没有执行到,不过也无所谓了,不影响 4 个关键的处理节点,不要片面的追求高测试覆盖率。

编写E2E 测试

在单元测试中我们展示了如何为 validateUser() 的每一个功能点编写单元测试,并且使用了 mock 数据的方法来确保每个功能点都能够被测试到。而在 e2e 测试中,我们需要模拟真实的用户场景,所以要连接数据库来进行测试。因此,这次测试的 auth.service.ts 模块里的方法都会和数据库进行交互。

auth 模块主要有以下几个功能:

  • 注册
  • 登录
  • 刷新 token
  • 读取用户信息
  • 修改密码
  • 删除用户

e2e 测试需要将这六个功能都测试一遍,从注册开始,到删除用户结束。在测试时,我们可以建一个专门的测试用户来进行测试,测试完成后再删除这个测试用户,这样就不会在测试数据库中留下无用的信息了。

beforeAll(async () => {
  const moduleFixture: TestingModule = await Test.createTestingModule({
    imports: [AppModule],
  }).compile()

  app = moduleFixture.createNestApplication()
  await app.init()

  // 执行登录以获取令牌
  const response = await request(app.getHttpServer())
    .post('/auth/register')
    .send({ username: TEST_USER_NAME, password: TEST_USER_PASSWORD })
    .expect(201)

  accessToken = response.body.access_token
  refreshToken = response.body.refresh_token
})

afterAll(async () => {
  await request(app.getHttpServer())
    .delete('/auth/delete-user')
    .set('Authorization', `Bearer ${accessToken}`)
    .expect(200)

  await app.close()
})

beforeAll 钩子函数将在所有测试开始之前执行,所以我们可以在这里注册一个测试账号 TEST_USER_NAMEafterAll 钩子函数将在所有测试结束之后执行,所以在这删除测试账号 TEST_USER_NAME 是比较合适的,还能顺便对注册和删除两个功能进行测试。

在上一节的单元测试中,我们编写了关于 validateUser 方法的相关单元测试。其实这个方法是在登录时执行的,用于验证用户账号密码是否正确。所以这一次的 e2e 测试也将使用登录流程来展示如何编写 e2e 测试用例。

整个登录测试流程总共包含了五个小测试:

describe('login', () => {
    it('/auth/login (POST)', () => {
      // ...
    })

    it('/auth/login (POST) with user not found', () => {
      // ...
    })

    it('/auth/login (POST) without username or password', async () => {
      // ...
    })

    it('/auth/login (POST) with invalid password', () => {
      // ...
    })

    it('/auth/login (POST) account lock after multiple failed attempts', async () => {
      // ...
    })
  })

这五个测试分别是:

  1. 登录成功,返回 200
  2. 如果用户不存在,抛出 401 异常
  3. 如果不提供密码或用户名,抛出 400 异常
  4. 使用错误密码登录,抛出 401 异常
  5. 如果账户被锁定,抛出 401 异常

现在我们开始编写 e2e 测试:

// 登录成功
it('/auth/login (POST)', () => {
  return request(app.getHttpServer())
    .post('/auth/login')
    .send({ username: TEST_USER_NAME, password: TEST_USER_PASSWORD })
    .expect(200)
})

// 如果用户不存在,应该抛出 401 异常
it('/auth/login (POST) with user not found', () => {
  return request(app.getHttpServer())
    .post('/auth/login')
    .send({ username: TEST_USER_NAME2, password: TEST_USER_PASSWORD })
    .expect(401) // Expect an unauthorized error
})

e2e 的测试代码写起来比较简单,直接调用接口,然后验证结果就可以了。比如登录成功测试,我们只要验证返回结果是否是 200 即可。

前面四个测试都比较简单,现在我们看一个稍微复杂点的 e2e 测试,即验证账户是否被锁定。

it('/auth/login (POST) account lock after multiple failed attempts', async () => {
  const moduleFixture: TestingModule = await Test.createTestingModule({
    imports: [AppModule],
  }).compile()

  const app = moduleFixture.createNestApplication()
  await app.init()

  const registerResponse = await request(app.getHttpServer())
    .post('/auth/register')
    .send({ username: TEST_USER_NAME2, password: TEST_USER_PASSWORD })

  const accessToken = registerResponse.body.access_token
  const maxLoginAttempts = 3 // lock user when the third try is failed

  for (let i = 0; i < maxLoginAttempts; i++) {
    await request(app.getHttpServer())
      .post('/auth/login')
      .send({ username: TEST_USER_NAME2, password: 'InvalidPassword' })
  }

  // The account is locked after the third failed login attempt
  await request(app.getHttpServer())
    .post('/auth/login')
    .send({ username: TEST_USER_NAME2, password: TEST_USER_PASSWORD })
    .then((res) => {
      expect(res.body.message).toContain(
        'The account is locked. Please try again in 5 minutes.',
      )
    })

  await request(app.getHttpServer())
    .delete('/auth/delete-user')
    .set('Authorization', `Bearer ${accessToken}`)

  await app.close()
})

当用户连续三次登录失败的时候,账户就会被锁定。所以在这个测试里,我们不能使用测试账号 TEST_USER_NAME,因为测试成功的话这个账户就会被锁定,无法继续进行下面的测试了。我们需要再注册一个新用户 TEST_USER_NAME2,专门用来测试账户锁定,测试成功后再删除这个用户。所以你可以看到这个 e2e 测试的代码非常多,需要做大量的前置、后置工作,其实真正的测试代码就这几行:

// 连续三次登录
for (let i = 0; i < maxLoginAttempts; i++) {
  await request(app.getHttpServer())
    .post('/auth/login')
    .send({ username: TEST_USER_NAME2, password: 'InvalidPassword' })
}

// 测试账号是否被锁定
await request(app.getHttpServer())
  .post('/auth/login')
  .send({ username: TEST_USER_NAME2, password: TEST_USER_PASSWORD })
  .then((res) => {
    expect(res.body.message).toContain(
      'The account is locked. Please try again in 5 minutes.',
    )
  })

可以看到编写 e2e 测试代码还是相对比较简单的,不需要考虑 mock 数据,不需要考虑测试覆盖率,只要整个系统流程的运转情况符合预期就可以了。

应不应该写测试

如果有条件的话,我是比较建议大家写测试的。因为写测试可以提高系统的健壮性、可维护性和开发效率。

提高系统健壮性

我们一般编写代码时,会关注于正常输入下的程序流程,确保核心功能正常运作。但是一些边缘情况,比如异常的输入,这些我们可能会经常忽略掉。但当我们开始编写测试时,情况就不一样了,这会逼迫你去考虑如何处理并提供相应的反馈,从而避免程序崩溃。可以说写测试实际上是在间接地提高系统健壮性。

提高可维护性

当你接手一个新项目时,如果项目包含完善的测试,那将会是一件很幸福的事情。它们就像是项目的指南,帮你快速把握各个功能点。只看测试代码就能够轻松地了解每个功能的预期行为和边界条件,而不用你逐行的去查看每个功能的代码。

提高开发效率

想象一下,一个长时间未更新的项目突然接到了新需求。改了代码后,你可能会担心引入 bug,如果没有测试,那就需要重新手动测试整个项目——浪费时间,效率低下。而有了完整的测试,一条命令就能得知代码更改有没有影响现有功能。即使出错了,也能够快速定位,找到问题点。

什么时候不建议写测试?

短期项目需求迭代非常快的项目不建议写测试。比如某些活动项目,活动结束就没用了,这种项目就不需要写测试。另外,需求迭代非常快的项目也不要写测试,我刚才说写测试能提高开发效率是有前提条件的,就是功能迭代比较慢的情况下,写测试才能提高开发效率。如果你的功能今天刚写完,隔一两天就需求变更了要改功能,那相关的测试代码都得重写。所以干脆就别写了,靠团队里的测试人员测试就行了,因为写测试是非常耗时间的,没必要自讨苦吃。

根据我的经验来看,国内的绝大多数项目(尤其是政企类项目)都是没有必要写测试的,因为需求迭代太快,还老是推翻之前的需求,代码都得加班写,那有闲情逸致写测试。

总结

在细致地讲解了如何为 Nestjs 项目编写单元测试及 e2e 测试之后,我还是想重申一下测试的重要性,它能够提高系统的健壮性、可维护性和开发效率。如果没有机会写测试,我建议大家可以自己搞个练习项目来写,或者说参加一些开源项目,给这些项目贡献代码,因为开源项目对于代码要求一般都比较严格。贡献代码可能需要编写新的测试用例或修改现有的测试用例。

最后,再推荐一下我的其他文章,如果你有兴趣,不妨一读:

  • 带你入门前端工程
  • 从零开始实现一个玩具版浏览器渲染引擎
  • 手把手教你写一个简易的微前端框架
  • 前端监控 SDK 的一些技术要点原理分析
  • 可视化拖拽组件库一些技术要点原理分析
  • 可视化拖拽组件库一些技术要点原理分析(二)
  • 可视化拖拽组件库一些技术要点原理分析(三)
  • 可视化拖拽组件库一些技术要点原理分析(四)
  • 低代码与大语言模型的探索实践
  • 前端性能优化 24 条建议(2020)
  • 手把手教你写一个脚手架
  • 手把手教你写一个脚手架(二)

参考资料

  • NestJS: A framework for building efficient, scalable Node.js server-side applications.
  • MongoDB: A NoSQL database used for data storage.
  • Jest: A testing framework for JavaScript and TypeScript.
  • Supertest: A library for testing HTTP servers.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

UDP 的报文结构

一.UDP的报文结构 1.UDP的简单介绍 UDP是传输层协议&#xff0c;它是无连接,不可靠传输,面向数据报,全双工 1.无连接&#xff1a;UDP是一种无连接的传输协议&#xff0c;通信双方不需要在发送数据之前建立连接。相比之下&#xff0c;TCP是面向连接的协议&#xff0c;在传输数…

【除了协程还有哪些方式可以实现异步编程】

在Unity中&#xff0c;除了使用协程实现异步编程外&#xff0c;还有以下几种方法&#xff1a; 异步加载资源&#xff1a; 使用UnityWebRequest类进行异步加载资源&#xff0c;这在加载网络资源或动态加载资源时非常有用。 using UnityEngine; using UnityEngine.Networking;…

【Linux】进程控制 之 进程创建 进程终止 进程等待 进程替换

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

每日一博 - 闲聊架构设计中的多级缓存设计

文章目录 方法论概述客户端缓存应用层缓存服务层缓存缓存设计的注意事项总结 思维导图戳这里 方法论概述 从客户端到服务层&#xff0c;缓存的应用广泛而重要。通过合理的缓存设计&#xff0c;能够有效地提高系统的性能并降低延迟。 客户端缓存 在客户端层面&#xff0c;浏览…

LLM2Vec介绍和将Llama 3转换为嵌入模型代码示例

嵌入模型是大型语言模型检索增强生成(RAG)的关键组成部分。它们对知识库和用户编写的查询进行编码。 使用与LLM相同领域的训练或微调的嵌入模型可以显著改进RAG系统。然而&#xff0c;寻找或训练这样的嵌入模型往往是一项困难的任务&#xff0c;因为领域内的数据通常是稀缺的。…

基于AT89C51单片机的温度上下限自动控制检报警设计

点击链接获取Keil源码与Project Backups仿真图: https://download.csdn.net/download/qq_64505944/89247694?spm=1001.2014.3001.5501 C 源码+仿真图+毕业设计+实物制作步骤+06 题 目 基于单片机的温度检测调节系统设计 姓 名 学 号 专业班级 指导教师 年 月 日 任务书 …

Nginx 从入门到实践(2)——Rewrite重写

Nginx Rewrite Rewrite重写 Nginx Rewriteurl组成说明Rewrite基本概述Rewrite使⽤场景rewrite优点 Rewrite配置语法location匹配概述 if指令if 判断指令语法nginx以及if 判断可使用的全局变量 set命令return指令 url组成说明 https://cn.bing.com/search?qNginxRewrite&P…

udp/tcp回显网络编程

udp DatagramSocket 用于接收和发送udp数据报 构造方法&#xff1a; DatagramSocket():创建一个UDP数据报套接字的Socket&#xff0c;绑定到本地上 一个随机可用端口上&#xff0c;一般用于客户端DatagramSocket(int port):创建一个UDP数据报套接字的Socket&#xff0c;绑定到…

Proxmox VE 8 用SDN隔离用户网络

作者&#xff1a;田逸&#xff08;formyz&#xff09; 最新发布的Proxmox VE&#xff08;以下简称PVE&#xff09; 8在Web管理后台集成了易于操作的SDN&#xff08;软件定义网络&#xff09;功能插件&#xff0c;其实质是对不同的PVE用户指定不同的网络&#xff0c;进行逻辑隔离…

将要上市的自动驾驶新书《自动驾驶系统开发》中摘录各章片段 4

第十三章 车联网 数字化设备正变得越来越普遍并且相互联系。这些设备向数字生态系统智能部分的演进创造了迄今为止尚未解决安全问题的新颖应用。一个特定的例子是车辆&#xff0c;随着车辆从简单的交通方式发展到具有新的感知和通讯功能的智能实体&#xff0c;就成为智能城市的…

屏蔽罩材质和厚度对屏蔽效能的影响

​ 一&#xff0e;屏蔽效能的影响因素 屏蔽效能的影响因素主要有两个方面&#xff1a;屏蔽材料的特性和厚度&#xff1b;如下图所示&#xff0c;电磁波经过不同媒介时&#xff0c;会在分界面形成反射&#xff0c;穿过界面的电磁波一部分被反射回去&#xff0c;这部分能量损失…

偶然发现了Python的一个BUG。。。

一般情况下&#xff0c;dict(id1, **{id: 1})这句代码应该报TypeError。但如果在捕获了其他异常的情况下&#xff0c;再来执行这句代码&#xff0c;却是会报KeyError&#xff0c;如下图&#xff1a; Python3.10和Python3.9也能复现该情况&#xff0c;正当我摩拳踩掌&#xff0c…

百度下拉框负面信息如何删除?

百度头条360等搜索引擎&#xff0c;作为人们获取信息的主要途径之一。然而&#xff0c;一些知名的企业或个人可能会面临在搜索的下拉框中出现负面信息的问题&#xff0c;这可能对其声誉和形象造成不良影响。小马识途营销顾问根据自身从业经验&#xff0c;针对这类情况提出以下建…

【精品毕设推荐】基于JSP物流信息网的设计与实现

点击免费下载原文及代码、PPT 摘要 本文讲述了基于JSP物流信息网的设计与实现。该系统使用java语言开发&#xff0c;使系统具有更好的平台性和可扩展性。 该系统实现了用户登录、注册、查询快递信息、快递公司注册成为合作伙伴以及系统管理员对信息进行管理等功能。系统的主…

LeetCode 234.回文链表

题目描述 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为 回文链表 。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff…

解决windows中的WSL Ubuntu子系统忘记root密码和用户密码问题

1、以管理员身份运行PowerShell 2、在powershell中执行wsl.exe --user root wsl.exe --user root如果出现了上面的报错&#xff0c;则需要运行步骤3、4&#xff0c;然后在执行步骤5改密码&#xff0c;如果没有出错&#xff0c;请直接跳到第5步改密码操作&#xff01;&#xff…

一分钱不花从HTTP升级到HTTPS

HTTP升级到HTTPS是一个涉及安全性和技术实施的过程&#xff0c;主要目的是为了提升网站数据传输的安全性&#xff0c;防止数据被窃取或篡改。以下是一些关于从HTTP升级到HTTPS的技术性要点和步骤概述&#xff0c;结合上述信息资源&#xff1a; 一、理解HTTPS的重要性 HTTPS (…

微信IDE vscode插件:获取插件位置,并打开文件

背景 有没有觉得在微信开发工具里面添加一些插件可以很方便。因为微信IDE的编辑本身是依赖vscode开发&#xff0c;所以编写vscode插件自然可以在微信IDE使用。这样做好处就是可以满足到自己一些开发使用习惯。 1.获取插件的目录位置 那么如何获取插件里面的目录&#xff0c;…

【精】hadoop、HIVE大数据从0到1部署及应用实战

目录 基本概念 Hadoop生态 HIVE hdfs(hadoop成员) yarn(hadoop成员) MapReduce(hadoop成员) spark flink storm HBase kafka ES 实战 安装并配置hadoop 环境准备 准备虚拟机 安装ssh并设置免密登录 安装jdk 安装、配置并启动hadoop 添加hadoop环境变量&…

STM32F1之FLASH闪存

目录 1. 简介 2. 闪存模块组织 3. FLASH基本结构 4. FLASH解锁 5. 使用指针访问存储器 6. 程序存储器全擦除 7. 程序存储器页擦除 8. 程序存储器编程 9. 选项字节 1. 简介 STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分&#xff0c;通过…