单元测试框架gtest学习(三)—— 事件机制

前言

上节我们学习了gtest的各种宏断言

单元测试框架gtest学习(二)—— 认识断言-CSDN博客

本节我们介绍gtets的事件机制

虽然 Google Test 的核心是用来编写单元测试和断言的,但它也允许在测试执行过程中进行事件的钩取和自定义,其主要用于处理测试生命周期中的各种操作,如测试的初始化、清理、事件通知等

简单来讲,事件机制的作用就是帮我们进行一些测试前的初始化工作和测试后的清理工作

介绍与引出

我们直接上代码进行说明,如下我们实现了一个简单的线程安全队列

// safequeue.h
#ifndef SAFEQUEUE_H
#define SAFEQUEUE_H#include <mutex>
#include <queue>
#include <stdexcept>template <typename E>
class Queue {public:Queue() = default; // 在此定义默认构造函数// 入队操作void Enqueue(const E &element) {std::lock_guard<std::mutex> lock(mtx_);queue_.emplace(element);}// 出队操作E Dequeue() {std::lock_guard<std::mutex> lock(mtx_);if (!queue_.empty()) {E data = queue_.front();queue_.pop();return data;}throw std::runtime_error("Queue is empty");}// 获取队列容量size_t size() const { return queue_.size(); }private:std::queue<E> queue_;std::mutex mtx_;
};#endif // SAFEQUEUE_H

代码写完之后,接下来我们自然想测试一下我们的队列入队操作、出队操作以及队列容量的获取操作这些功能是否符合我们的预期

因此,我们会写出以下测试案例

TEST(QueueTest, DeQueueTest) {Queue<int> queue;queue.Enqueue(1);queue.Enqueue(2);queue.Enqueue(3);for (int i = 1; i <= 3; ++i) {try {int n = queue.Dequeue();// 测试是否出队成功,并且出队的元素符合预期EXPECT_EQ(n, i);} catch (const std::runtime_error &e) {// 如果抛出异常,判断抛出异常的原因是否是因为队列为空EXPECT_STREQ(e.what(), "Queue is empty");}}
}TEST(QueueTest, QueueSize) {Queue<int> queue;queue.Enqueue(1);queue.Enqueue(2);queue.Enqueue(3);EXPECT_EQ(queue.size(), 3);
}

编译运行,查看结果

从结果上看,

  • 我们进行了两个测试案例
    • 一个用于测试队列的出队操作
    • 另一个用于测试队列的容量是否符合预期
  • 这两个测试案例的被测对象都同属于一个,即QueueTest

由此,我们引出两个概念

  •  test suite:直译为测试套件,在上述代码中指QueueTest
  •  test case:直译为测试案例,上述代码中指DeQueueTest和QueueSize

因此,直观上看, test suite可以理解为我们被测的对象是谁,而 test case可以理解我们要测试的功能是哪些,其中这些功能是属于上述被测对象的

关于测试套件和测试案例的概念点到为止,接下来我们将目光继续放在代码上

观察上述两个功能的测试

  • 我们在对每个功能进行测试前,都对队列进行了入队操作
  • 这个入队操作从另一个角度可以理解为测试前的初始化操作(毕竟只有队列中有数据才能进行出队和容量测试) 

显而易见的,当我们的测试功能越来越多时,这种类同的初始化工作每次都要写一遍显然是在浪费时间和效率,同样的道理,在测试结束之后我们也是需要进行清理工作的,只是恰好我们这里的清理工作每次都由析构函数做了

因此,对于一些测试而言,我们需要在每个测试的前后进行一些初始化操作和清理工作,那么问题来了,有没有一种办法能够一次性将这些初始化和清理工作完成,而不必我们每次都要在写测试案例的手动进行处理呢?

这就引出了我们今天要说的事件机制,直观上看,事件机制的作用好像是构造函数和析构函数的作用

事件机制分类

事件机制分为三类:

1. 全局的,也就是在所有案例执行前进行初始化操作,在所有案例结束后进行清理操作。

2. TestSuite级别的,在某一批案例中第一个案例前进行初始化操作,最后一个案例执行后进行清理操作。

3. TestCase级别的,每个TestCase前后都进行一次初始化和清理。

全局事件

全局事件主要用于对全局环境的初始化和清理

要实现全局事件,必须写一个类,继承testing::Environment类,实现里面的SetUp和TearDown方法,其中

1. SetUp()方法在所有案例执行前执行

2. TearDown()方法在所有案例执行后执行

#if 1
class MyTestEnvironment : public testing::Environment {public:// 在所有测试开始之前调用void SetUp() override {std::cout << "Global SetUp: 初始化全局事件\n";// 在这里进行全局初始化,比如资源分配}// 在所有测试完成之后调用void TearDown() override {std::cout << "Global TearDown: 清理全局事件\n";// 在这里进行全局清理,比如释放资源}
};// 测试夹具类
class QueueTest : public ::testing::Test {protected:void SetUp() override {// 在每个测试用例前执行std::cout << "Test SetUp: 准备测试环境\n";}void TearDown() override {// 在每个测试用例后执行std::cout << "Test TearDown: 清理测试环境\n";}
};// 定义你的测试用例
TEST_F(QueueTest, IsEmptyInitially) {std::cout << "执行 IsEmptyInitially 测试\n";// 这里可以写你的测试逻辑
}TEST_F(QueueTest, DequeueWorks) {std::cout << "执行 DequeueWorks 测试\n";// 这里可以写你的测试逻辑
}
#endif

我们还需要告诉gtest添加这个全局事件,我们需要在main函数中通过testing::AddGlobalTestEnvironment方法将事件挂进来,也就是说,我们可以写很多个这样的类,然后将他们的事件都挂上去。

int main(int argc, char **argv) {// 注册我们自定义的全局环境testing::AddGlobalTestEnvironment(new MyTestEnvironment);testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}

另外,我们观察上述代码,我们的断言宏已经不再是TEST,而是TEST_F了,这一点需要注意,如果我们使用事件机制进行测试,则需要使用TEST_F,而不能使用TEST宏 

编译运行

TestSuite事件

同理TestSuite事件也需要我们写一个类,继承testing::Test,然后实现两个静态方法

1. SetUpTestSuite() 方法在第一个TestCase之前执行

2. TearDownTestSuite() 方法在最后一个TestCase之后执行


#if 1
class QueueTest : public testing::Test {protected:// 这是每个测试用例的初始化void SetUp() override {std::cout << "  初始化每个TestCase环境...." << std::endl;q1_.Enqueue(1);q2_.Enqueue(2);q2_.Enqueue(3);}// 这是每个测试用例的清理void TearDown() override {std::cout << "  清理每个TestCase环境...." << std::endl;}// 用于测试的队列对象Queue<int> q0_;Queue<int> q1_;Queue<int> q2_;public:// 静态方法,用于测试套件级别的初始化static void SetUpTestSuite() {std::cout << "初始化整个TestSuite环境...." << std::endl;}// 静态方法,用于测试套件级别的清理static void TearDownTestSuite() {std::cout << "清理整个TestSuite环境...." << std::endl;}
};TEST_F(QueueTest, IsEmptyInitially) { EXPECT_EQ(q0_.size(), 0); }TEST_F(QueueTest, DequeueWorks) {try {int n = q0_.Dequeue();FAIL() << "Expected exception for empty queue";} catch (const std::runtime_error &e) {EXPECT_STREQ(e.what(), "Queue is empty");}int n = q1_.Dequeue();EXPECT_EQ(n, 1);EXPECT_EQ(q1_.size(), 0);n = q2_.Dequeue();EXPECT_EQ(n, 2);EXPECT_EQ(q2_.size(), 1);n = q2_.Dequeue();EXPECT_EQ(n, 3);EXPECT_EQ(q2_.size(), 0);
}
#endif

编译运行

TestCase事件

TestCase事件是挂在每个案例执行前后的,实现方式和上面的几乎一样,不过需要实现的是SetUp方法和TearDown方法:

1. SetUp()方法在每个TestCase之前执行

2. TearDown()方法在每个TestCase之后执行

仍旧使用上述案例代码,观察运行结果发现

实战

接下来我们写一个实际案例来运用上述事件机制

 首先我们实现了一个简单的线程池,然后我们来测试这个线程池的运行是否可以正常运行(注意这里是测试功能是否符合预期,而不是进行压测)

//threadPool.h
#ifndef THREADPOOL_H
#define THREADPOOL_H#include <atomic>
#include <condition_variable>
#include <functional>
#include <future>
#include <memory>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>class ThreadPool {public:ThreadPool(int size = std::thread::hardware_concurrency()): pool_size_(size), isStop_(false) {for (int i = 0; i < pool_size_; ++i) {// threads_.push_back(std::thread(&ThreadPool::worker,this));threads_.emplace_back([this]() { worker(); });}}~ThreadPool() { ShutDown(); }void ShutDown() {isStop_ = true;not_empty_cond_.notify_all();for (auto &thread : threads_) {if (thread.joinable())thread.join();}}template <typename F, typename... Args>auto Submit(F &&f, Args &&...args) -> std::future<decltype(f(args...))> {using func_type = decltype(f(args...));auto task_ptr = std::make_shared<std::packaged_task<func_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));std::future<func_type> func_future = task_ptr->get_future();{std::lock_guard<std::mutex> lock(mtx_);if (isStop_)throw std::runtime_error("threadpool has stop!!!");task_queue_.emplace([task_ptr]() { (*task_ptr)(); });}not_empty_cond_.notify_one();return func_future;}private:std::atomic_bool isStop_;int pool_size_;using Task = std::function<void()>;std::vector<std::thread> threads_;std::queue<Task> task_queue_;std::mutex mtx_;std::condition_variable not_empty_cond_;void worker() {while (1) {std::unique_lock<std::mutex> lock(mtx_);not_empty_cond_.wait(lock, [this]() { return isStop_ || !task_queue_.empty(); });if (isStop_ && task_queue_.empty())return;Task task = task_queue_.front();task_queue_.pop();lock.unlock();task();}return;}
};#endif // THREADPOOL_H

接下来我们写了两个函数来测试线程池是否运行正常

int add(const int &num1, const int &num2) { return num1 + num2; }

如果不使用gtest,我们对上述函数的测试代码是这样的

void addTest() {ThreadPool pool;auto task1 = pool.Submit(add, 1, 2);auto task2 = pool.Submit(add, 3, 4);int sum = task1.get() + task2.get();std::cout << "sum=" << sum << std::endl;
}

 显然,如果上述打印输出的结果sum等于10,说明符合预期,测试通过


// 矩阵乘法
std::vector<std::vector<int>>
MatrixMultiply(const std::vector<std::vector<int>> &A,const std::vector<std::vector<int>> &B, ThreadPool &pool) {size_t m = A.size();    // A的行数size_t n = A[0].size(); // A的列数size_t p = B[0].size(); // B的列数std::vector<std::vector<int>> C(m, std::vector<int>(p, 0)); // 结果矩阵std::vector<std::future<void>> futures;// 为结果矩阵中的每个元素提交一个计算任务for (size_t i = 0; i < m; ++i) {for (size_t j = 0; j < p; ++j) {futures.push_back(pool.Submit([i, j, &A, &B, &C, n]() {int sum = 0;for (size_t k = 0; k < n; ++k) {sum += A[i][k] * B[k][j];}C[i][j] = sum;}));}}// 等待所有的任务完成for (auto &future : futures) {future.get();}return C;
}

同理,对于上述矩阵乘法,如果我们不使用gtest来测试,代码应该是这样的

std::ostream &operator<<(std::ostream &os,std::vector<std::vector<int>> &nums) {int col = 0;int m = nums.size();int n = nums[0].size();for (int i = 0; i < m; ++i) {for (int j = 0; j < n; ++j) {os << nums[i][j] << " ";col++;if (col % n == 0)os << "\n";}}return os;
}void multiTest() {// 定义两个矩阵 A 和 Bstd::vector<std::vector<int>> A = {{1, 2, 3}, {4, 5, 6}};std::vector<std::vector<int>> B = {{7, 8}, {9, 10}, {11, 12}};ThreadPool pool(A.size() * B[0].size());// 计算矩阵 A 和 B 的乘积std::vector<std::vector<int>> C =MatrixMultiply(A, B, pool); // 使用 *pool_ 来解引用智能指针// std::vector<std::vector<int>> expected = {{58, 64}, {139, 154}};std::cout << C;
}

其实就是很简单的,把函数运行一下,然后打印输出结果,观察运行结果是否符合预期

只是在gtest中使用各种断言宏来帮助我们判断,而不是打印出来然后肉眼我们判断

好,接下来我们使用gtest来进行测试

既然我们要进行两个功能的测试(加法功能和矩阵乘法功能),每个功能在在测试之前都需要对线程池进行初始化,那么我们使用上述事件机制,进行一些初始化工作

class ThreadPoolTest : public ::testing::Test {public:void SetUp() override {std::cout << ">>>> init threadpool...." << std::endl;pool_ = std::make_unique<ThreadPool>(4); // 假设线程池初始化为4个线程}void TearDown() override {std::cout << ">>>> shutdown threadPool...." << std::endl;pool_->ShutDown();}std::unique_ptr<ThreadPool> pool_;
};

这样我们就不用在每个测试案例进行初始化工作了,只需要关注测试代码的逻辑编写即可

以下是加法功能的测试代码 

TEST_F(ThreadPoolTest, TestThreadPoolSubmit) {// 提交加法任务auto task1 = pool_->Submit(add, 1, 2); // 1 + 2auto task2 = pool_->Submit(add, 3, 4); // 3 + 4// 获取任务的结果int sum = task1.get() + task2.get(); // 获取两个任务的结果// 验证最终结果EXPECT_EQ(sum, 10);
}

以下是矩阵乘法的测试代码 

TEST_F(ThreadPoolTest, TestMatrixMultiplication) {// 定义两个矩阵 A 和 Bstd::vector<std::vector<int>> A = {{1, 2, 3}, {4, 5, 6}};std::vector<std::vector<int>> B = {{7, 8}, {9, 10}, {11, 12}};// 计算矩阵 A 和 B 的乘积std::vector<std::vector<int>> C =MatrixMultiply(A, B, *pool_); // 使用 *pool_ 来解引用智能指针// 验证结果矩阵 Cstd::vector<std::vector<int>> expected = {{58, 64}, {139, 154}};EXPECT_EQ(C, expected);
}

完整代码如下

#include "threadPool.h"
#include <gtest/gtest.h>#if 1
class ThreadPoolTest : public ::testing::Test {public:void SetUp() override {std::cout << ">>>> init threadpool...." << std::endl;pool_ = std::make_unique<ThreadPool>(4); // 假设线程池初始化为4个线程}void TearDown() override {std::cout << ">>>> shutdown threadPool...." << std::endl;pool_->ShutDown();}std::unique_ptr<ThreadPool> pool_;
};// 矩阵乘法
std::vector<std::vector<int>>
MatrixMultiply(const std::vector<std::vector<int>> &A,const std::vector<std::vector<int>> &B, ThreadPool &pool) {size_t m = A.size();    // A的行数size_t n = A[0].size(); // A的列数size_t p = B[0].size(); // B的列数std::vector<std::vector<int>> C(m, std::vector<int>(p, 0)); // 结果矩阵std::vector<std::future<void>> futures;// 为结果矩阵中的每个元素提交一个计算任务for (size_t i = 0; i < m; ++i) {for (size_t j = 0; j < p; ++j) {futures.push_back(pool.Submit([i, j, &A, &B, &C, n]() {int sum = 0;for (size_t k = 0; k < n; ++k) {sum += A[i][k] * B[k][j];}C[i][j] = sum;}));}}// 等待所有的任务完成for (auto &future : futures) {future.get();}return C;
}
// 验证矩阵乘法结果是否正确
TEST_F(ThreadPoolTest, TestMatrixMultiplication) {// 定义两个矩阵 A 和 Bstd::vector<std::vector<int>> A = {{1, 2, 3}, {4, 5, 6}};std::vector<std::vector<int>> B = {{7, 8}, {9, 10}, {11, 12}};// 计算矩阵 A 和 B 的乘积std::vector<std::vector<int>> C =MatrixMultiply(A, B, *pool_); // 使用 *pool_ 来解引用智能指针// 验证结果矩阵 Cstd::vector<std::vector<int>> expected = {{58, 64}, {139, 154}};EXPECT_EQ(C, expected);
}int add(const int &num1, const int &num2) { return num1 + num2; }
TEST_F(ThreadPoolTest, TestThreadPoolSubmit) {// 提交加法任务auto task1 = pool_->Submit(add, 1, 2); // 1 + 2auto task2 = pool_->Submit(add, 3, 4); // 3 + 4// 获取任务的结果int sum = task1.get() + task2.get(); // 获取两个任务的结果// 验证最终结果EXPECT_EQ(sum, 10);
}
#endif#if 1
int main(int argc, char **argv) {// testing::AddGlobalTestEnvironment(new FooEnvironment);testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
#endif

运行结果如下所示

[ 11:08上午 ]  [ pcl@robot:~/projects/myPro/threadPool/test(main✗) ]$ g++ ./gTest.cc -o gtest -std=c++14 -lgtest -lpthread
[ 11:08上午 ]  [ pcl@robot:~/projects/myPro/threadPool/test(main✗) ]$ ./gtest
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from ThreadPoolTest
[ RUN      ] ThreadPoolTest.TestMatrixMultiplication
>>>> init threadpool....
>>>> shutdown threadPool....
[       OK ] ThreadPoolTest.TestMatrixMultiplication (3 ms)
[ RUN      ] ThreadPoolTest.TestThreadPoolSubmit
>>>> init threadpool....
>>>> shutdown threadPool....
[       OK ] ThreadPoolTest.TestThreadPoolSubmit (1 ms)
[----------] 2 tests from ThreadPoolTest (4 ms total)[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (5 ms total)
[  PASSED  ] 2 tests.

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在k8s上部署Crunchy Postgres for Kubernetes

目录 一、前言二、安装Crunchy Postgres for Kubernetes三、部署一个简单的postgres集群四、增加pgbouncer五、数据备份六、备份恢复七、postgres配置参数八、数据导入九、权限管理 一、前言 Crunchy Postgres可以帮助我们在k8s上快速部署一个高可用、具有自动备份和恢复功能的…

python中Pandas操作excel补全内容

补全ID、InStore、Date import random from datetime import datetime, timedeltaimport pandas as pdfile_path r"C:\Users\xb\Desktop\Books_1.xlsx" books pd.read_excel(iofile_path, skiprows3, usecols"C:F", dtype{"ID": str, "I…

高级 SQL 技巧讲解

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言&#xff1a; SQL&#xff08;结构化查询语言&#xff09;是管理和操作数据库的核心工具。从基本的查询语句到复杂的数据处理&#xff0c;掌握高级 SQL 技巧不仅能显著提高数据分析的效率&#xff0c;还能解决业务中的复…

Android okhttp 网络链接各阶段监控

步骤 1: 添加依赖 在项目的 build.gradle 文件中&#xff0c;添加 OkHttp 依赖&#xff1a; implementation com.squareup.okhttp3:okhttp:4.11.0 步骤 2: 创建自定义的 EventListener 创建一个自定义的 EventListener 类&#xff1a; import android.util.Log import okht…

springboot高校毕业生实习及就业去向信息管理系统

摘 要 高校毕业生实习及就业去向信息管理管理系统采用B/S架构&#xff0c;数据库是MySQL。网站的搭建与开发采用了先进的java进行编写&#xff0c;使用了springboot框架。该系统从三个对象&#xff1a;由管理员和学生、企业信息来对系统进行设计构建。主要功能包括&#xff1a…

网络安全概论——网络安全基础

一、网络安全引言 信息安全的四个属性&#xff08;信息安全的基本目标 &#xff09; 保密性:信息不会被泄露给非授权用户完整性&#xff1a;保证数据的一致性可用性&#xff1a;合法用户不会被拒绝服务合法使用&#xff1a;不会被非授权用户或以非授权的方式使用 二、网络安…

【Unity基础】认识Unity中的包

Unity中的包是一个核心概念&#xff0c;像Unity本身的功能的扩展&#xff0c;或者项目中资源的管理&#xff0c;都是通过包的形式来实现的。 一、什么是包&#xff1f; 一个包包含满足您项目各种需求的功能。这可以包括编辑器安装过程中附带的任何核心Unity功能&#xff0c;也…

【从零开始的LeetCode-算法】3354. 使数组元素等于零

给你一个整数数组 nums 。 开始时&#xff0c;选择一个满足 nums[curr] 0 的起始位置 curr &#xff0c;并选择一个移动 方向 &#xff1a;向左或者向右。 此后&#xff0c;你需要重复下面的过程&#xff1a; 如果 curr 超过范围 [0, n - 1] &#xff0c;过程结束。如果 nu…

VUE+SPRINGBOOT实现邮箱注册、重置密码、登录功能

随着互联网的发展&#xff0c;网站用户的管理、触达、消息通知成为一个网站设计是否合理的重要标志。目前主流互联网公司都支持手机验证码注册、登录。但是手机短信作为服务端网站是需要付出运营商通信成本的&#xff0c;而邮箱的注册、登录、重置密码&#xff0c;无疑成为了这…

ChatGPT Search VS Kimi探索版:AI搜索哪家强?!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;专注于分享AI全维度知识&#xff0c;包括但不限于AI科普&#xff0c;AI工…

【网络系统管理】Centos7——配置主从mariadb服务器案例(下半部分)

【网络系统管理】Centos7——配置主从mariadb服务器案例-CSDN博客 接上个文档&#xff0c;我们已经完成了主服务器创建数据库备服务器可以看到 一、在DBMS2查看信息 File&#xff0c;Position这两个字段的数据要记好&#xff0c;等一下需要用到 show master status; 二、在…

定长滑动窗口(LeetCode——1423.可获得的最大点数)

题目 . - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/maximum-points-you-can-obtain-from-cards 几张卡牌 排成一行…

ubuntu20.04如何升级python3.8到python3.10

主要参考了这两个链接&#xff1a; 如何在Ubuntu 20.04安装Python 3.10 | myfreaxhttps://www.myfreax.com/how-to-install-python-3-10-on-ubuntu-20-04/#:~:text%E5%9C%A8%E8%B0%83%E8%AF%95%E5%92%8C%E5%85%B6%E4%BB%96%E5%B7%A5%E5%85%B7%E4%B8%AD%E4%BD%BF%E7%94%A8%E7%B…

JDK安装和Linux常见设置详细版教程

一、Linux的常见设置 1、设置静态IP vi /etc/sysconfig/network-scripts/ifcfg-ens33 如何查看自己的虚拟机的网关&#xff1a; 完整的配置&#xff08;不要拷贝我的&#xff09;&#xff1a; TYPE"Ethernet" PROXY_METHOD"none" BROWSER_ONLY"no&…

【Visual Studio系列教程】如何在 VS 上编程?

上一篇博客中&#xff0c;我们介绍了《什么是 Visual Studio&#xff1f;》。本文&#xff0c;我们来看第2篇《如何在 VS 上编程&#xff1f;》。阅读本文大约10 分钟。我们会向文件中添加代码&#xff0c;了解 Visual Studio 编写、导航和了解代码的简便方法。 本文假定&…

python: generator model using sql server 2019

設計或生成好數據庫&#xff0c;可以生成自己設計好的框架項目 # encoding: utf-8 # 版权所有 &#xff1a;2024 ©涂聚文有限公司 # 许可信息查看 &#xff1a;言語成了邀功盡責的功臣&#xff0c;還需要行爲每日來值班嗎 # 描述&#xff1a; : 生成实体 # Author …

【Rabbitmq篇】RabbitMQ⾼级特性----消息确认

目录 前言&#xff1a; 一.消息确认机制 • ⾃动确认 • ⼿动确认 手动确认方法又分为三种&#xff1a; 二. 代码实现&#xff08;spring环境&#xff09; 配置相关信息&#xff1a; 1&#xff09;. AcknowledgeMode.NONE 2 &#xff09;AcknowledgeMode.AUTO 3&…

【Pikachu】SSRF(Server-Side Request Forgery)服务器端请求伪造实战

尽人事以听天命 1.Server-Side Request Forgery服务器端请求伪造学习 SSRF&#xff08;服务器端请求伪造&#xff09;攻击的详细解析与防范 SSRF&#xff08;Server-Side Request Forgery&#xff0c;服务器端请求伪造&#xff09; 是一种安全漏洞&#xff0c;它允许攻击者通…

鸿蒙NEXT自定义组件:太极Loading

【引言】&#xff08;完整代码在最后面&#xff09; 本文将介绍如何在鸿蒙NEXT中创建一个自定义的“太极Loading”组件&#xff0c;为你的应用增添独特的视觉效果。 【环境准备】 电脑系统&#xff1a;windows 10 开发工具&#xff1a;DevEco Studio NEXT Beta1 Build Vers…

JSONObject jsonObject = JSON.parseObject(json);

是用于将一个 JSON 格式的字符串解析为一个 JSONObject 对象的语句。具体来说&#xff1a; JSON.parseObject(json)&#xff1a; 作用&#xff1a; JSON 是 FastJSON 库提供的一个工具类。parseObject 方法可以将 JSON 格式的字符串&#xff08;例如&#xff1a;{"key1&qu…