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

C++单元测试GoogleTest和GoogleMock十分钟快速上手(gtestgmock)

C++单元测试GoogleTest和GoogleMock(gtest&gmock)

环境准备

下载

git clone https://github.com/google/googletest.git
# 或者
wget https://github.com/google/googletest/releases/tag/release-1.11.0

安装

cd googletest
cmake CMakeLists.txt 
make
sudo make install

重要文件

googletest
  • gtest/gtest.h
  • libgtest.a
  • libgtest_main.a

当不想写 main 函数的时候,可以直接引入 libgtest_main.a;

g++ sample.cc -o sample -lgtest -lgtest_main -lpthread 
g++ sample.cc -o sample -lgmock -lgmock_main -lpthread

否则

g++ sample.cc -o sample -lgtest -lpthread
googlemock
  • gmock/gmock.h
  • libgmock.a
  • libgmock_main.a

GoogleTest

一 .断言

gtest中的断言分成两大类:

  1. ASSERT_\*系列:如果检测失败就直接退出当前函数

  2. EXPECT_\*系列:如果检测失败发出提示,并继续往下执行

通常情况应该首选使用EXPECT_,因为ASSERT_在报告完错误后不会进行清理工作,有可能导致内存泄露问题。

gtest有很多类似的宏用来判断数值的关系、判断条件的真假、判断字符串的关系。

条件判断
ASSERT_TRUE(condition);  // 判断条件是否为真
ASSERT_FALSE(condition); // 判断条件是否为假EXPECT_TRUE(condition);  // 判断条件是否为真
EXPECT_FALSE(condition); // 判断条件是否为假
数值比较
ASSERT_EQ(val1, val2); // 判断是否相等
ASSERT_NE(val1, val2); // 判断是否不相等
ASSERT_LT(val1, val2); // 判断是否小于
ASSERT_LE(val1, val2); // 判断是否小于等于
ASSERT_GT(val1, val2); // 判断是否大于
ASSERT_GE(val1, val2); // 判断是否大于等于EXPECT_EQ(val1, val2); // 判断是否相等
EXPECT_NE(val1, val2); // 判断是否不相等
EXPECT_LT(val1, val2); // 判断是否小于
EXPECT_LE(val1, val2); // 判断是否小于等于
EXPECT_GT(val1, val2); // 判断是否大于
EXPECT_GE(val1, val2); // 判断是否大于等于
字符串比较
ASSERT_STREQ(str1,str2); // 判断字符串是否相等
ASSERT_STRNE(str1,str2); // 判断字符串是否不相等
ASSERT_STRCASEEQ(str1,str2); // 判断字符串是否相等,忽视大小写
ASSERT_STRCASENE(str1,str2); // 判断字符串是否不相等,忽视大小写EXPECT_STREQ(str1,str2); // 判断字符串是否相等
EXPECT_STRNE(str1,str2); // 判断字符串是否不相等
EXPECT_STRCASEEQ(str1,str2); // 判断字符串是否相等,忽视大小写
EXPECT_STRCASENE(str1,str2); // 判断字符串是否不相等,忽视大小写
谓词断言

谓词断言能比 EXPECT_TRUE 提供更详细的错误消息;

EXPECT_PRED1(pred,val1);
EXPECT_PRED2(pred,val1,val2);
EXPECT_PRED3(pred,val1,val2,val3);
EXPECT_PRED4(pred,val1,val2,val3,val4);
EXPECT_PRED5(pred,val1,val2,val3,val4,val5);ASSERT_PRED1(pred,val1);
ASSERT_PRED2(pred,val1,val2);
ASSERT_PRED3(pred,val1,val2,val3);
ASSERT_PRED4(pred,val1,val2,val3,val4);
ASSERT_PRED5(pred,val1,val2,val3,val4,val5);
// Returns true if m and n have no common divisors except 1. 
bool MutuallyPrime(int m, int n) { ... }
...
const int a = 3;
const int b = 4;
const int c = 10;
...
EXPECT_PRED2(MutuallyPrime, a, b);  // Succeeds
EXPECT_PRED2(MutuallyPrime, b, c);  // Fails

能得到错误信息:

MutuallyPrime(b, c) is false, where 
b is 4
c is 10

二 .宏测试

如果自己编写mian函数,那么需要调用testing::InitGoogleTest函数进行初始化然后调用RUN_ALL_TESTS(); 函数执行所有的测试集

TEST

进一步,为了更好的组织test cases,比如针对Factorial函数,输入是负数的cases为一组,输入是0的case为一组,正数cases为一组。gtest提供了一个宏TEST(TestSuiteName, TestName),用于组织不同场景的cases,这个功能在gtest中称为test suite

原型
#define TEST(test_suite_name,test_name)
代码示例

TEST_F()宏的第一个参数(即test_suite_name的名称)必须是测试装置类的类名。

TEST(test_suite_name,test_name)
{//可以像普通函数一样定义变量之类的行为。EXPECT_TRUE(condition);EXPECT_EQ(val1, val2);EXPECT_PRED1(pred,val1);
}
TEST_F

我们想让多个Test使用同一套数据配置时,就需要用到测试装置,创建测试装置的具体方法如下:

  • 派生一个继承 ::testing::Test 的类,并将该类中的一些内容声明为 protected 类型,以便在子类中进行访问;
  • 根据实际情况,编写默认的构造函数或SetUp()函数,来为每个 test 准备所需内容;
  • 根据实际情况,编写默认的析构函数或TearDown()函数,来释放SetUp()中分配的资源;
  • 根据实际情况,定义 test 共享的子程序。

TEST_F()宏的第一个参数(即Test Case的名称)必须是测试装置类的类名。

它继承testing::Test类,然后根据我们的需要实现下面这两个虚函数:

  • virtual void SetUp()类似于构造函数,总是在测试用例开始时被调用
  • virtual void TearDown()类似于析构函数,总是在测试用例结束后被调用

此外,testing::Test还提供了两个static函数:

  • static void SetUpTestSuite():在第一个TEST之前运行
  • static void TearDownTestSuite():在最后一个TEST之后运行
代码示例
class QueueTestSmpl3 : public testing::Test { // 继承了 testing::Test
protected:  static void SetUpTestSuite() {std::cout<<"run before first case..."<<std::endl;} static void TearDownTestSuite() {std::cout<<"run after last case..."<<std::endl;}virtual void SetUp() override {std::cout<<"enter into SetUp()" <<std::endl;q1_.Enqueue(1);q2_.Enqueue(2);q2_.Enqueue(3);}virtual void TearDown() override {std::cout<<"exit from TearDown" <<std::endl;}static int Double(int n) {return 2*n;}void MapTester(const Queue<int> * q) {const Queue<int> * const new_q = q->Map(Double);ASSERT_EQ(q->Size(), new_q->Size());for (const QueueNode<int>*n1 = q->Head(), *n2 = new_q->Head();n1 != nullptr; n1 = n1->next(), n2 = n2->next()) {EXPECT_EQ(2 * n1->element(), n2->element());}delete new_q;}Queue<int> q0_;Queue<int> q1_;Queue<int> q2_;
};
测试集代码
// in sample3_unittest.cc// Tests the default c'tor.
TEST_F(QueueTestSmpl3, DefaultConstructor) {// !!! 在 TEST_F 中可以使用 QueueTestSmpl3 的成员变量、成员函数 EXPECT_EQ(0u, q0_.Size());
}// Tests Dequeue().
TEST_F(QueueTestSmpl3, Dequeue) {int * n = q0_.Dequeue();EXPECT_TRUE(n == nullptr);n = q1_.Dequeue();ASSERT_TRUE(n != nullptr);EXPECT_EQ(1, *n);EXPECT_EQ(0u, q1_.Size());delete n;n = q2_.Dequeue();ASSERT_TRUE(n != nullptr);EXPECT_EQ(2, *n);EXPECT_EQ(1u, q2_.Size());delete n;
}// Tests the Queue::Map() function.
TEST_F(QueueTestSmpl3, Map) {MapTester(&q0_);MapTester(&q1_);MapTester(&q2_);
}
运行结果
% ./sample3_unittest
Running main() from /Users/self_study/Cpp/OpenSource/demo/include/googletest/googletest/samples/sample3_unittest.cc
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from QueueTestSmpl3
run before first case...    # 所有的test case 之前运行
[ RUN      ] QueueTestSmpl3.DefaultConstructor
enter into SetUp()          # 每次都会运行
exit from TearDown
[       OK ] QueueTestSmpl3.DefaultConstructor (0 ms)
[ RUN      ] QueueTestSmpl3.Dequeue
enter into SetUp()          # 每次都会运行
exit from TearDown
[       OK ] QueueTestSmpl3.Dequeue (0 ms)
[ RUN      ] QueueTestSmpl3.Map
enter into SetUp()          # 每次都会运行
exit from TearDown
[       OK ] QueueTestSmpl3.Map (0 ms)
run after last case...      # 所有test case结束之后运行
[----------] 3 tests from QueueTestSmpl3 (0 ms total)[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (0 ms total)
[  PASSED  ] 3 tests.

GoogleMock

当你写一个原型或测试,往往不能完全的依赖真实对象。一个 mock 对象实现与一个真实对象相同的接口,但让你在运行时指定它时,如何使用?它应该做什么?(哪些方法将被调用?什么顺序?多少次?有什么参数?会返回什么?等)

可以模拟检查它自己和调用者之间的交互;

mock 用于创建模拟类和使用它们;

  • 使用一些简单的宏描述你想要模拟的接口,他们将扩展到你的 mock 类的实现;
  • 创建一些模拟对象,并使用直观的语法指定其期望和行为;
  • 练习使用模拟对象的代码。 Google Mock会在出现任何违反期望的情况时立即处理。

注意

googlemock 依赖 googletest;调用 InitGoogleMock 时会自动调用 InitGoogleTest ;

头文件 #include “gmock/gmock.h”

什么时候使用?

  • 测试很慢,依赖于太多的库或使用昂贵的资源;
  • 测试脆弱,使用的一些资源是不可靠的(例如网络);
  • 测试代码如何处理失败(例如,文件校验和错误),但不容易造成;
  • 确保模块以正确的方式与其他模块交互,但是很难观察到交互;因此你希望看到观察行动结束时的副作用;
  • 想模拟出复杂的依赖;

使用方法

我们假设一个支付场景逻辑开发业务。我们开发复杂的业务模块,而团队其他成员开发用户行为模块。他们和我们约定了如下接口

class User {
public:User() {};~User() {};
public:// 登录virtual bool Login(const std::string& username, const std::string& password) = 0;// 支付virtual bool Pay(int money) = 0;// 是否登录virtual bool Online() = 0;
};

我们的业务模块要让用户登录,并发起支付行为。于是我们的代码如下

class Biz {
public:void SetUser(User* user) {_user = user;}std::string pay(const std::string& username, const std::string& password, int money) {std::string ret;if (!_user) {ret = "pointer is null.";return ret;}if (!_user->Online()) {ret = "logout status.";// 尚未登录,要求登录if (!_user->Login(username, password)) {// 登录失败ret += "login error.";return ret;} else {// 登录成功ret += "login success.";}} else {// 已登录ret = "login.status";}if (!_user->Pay(money)) {ret += "pay error.";} else {ret += "pay success.";}return ret;}private:User* _user;
};

第一步我们需要Mock接口类

  • MOCK_METHOD0(FUNC, TYPE);第一个参数填写函数名,第二个参数填写函数类型
  • MOCK_METHOD()后面的数字表示需要几个参数
  • const成员方法使用MOCK_CONST_METHOD系列
class TestUser : public User {
public:MOCK_METHOD2(Login, bool(const std::string&, const std::string&));MOCK_METHOD1(Pay, bool(int));MOCK_METHOD0(Online, bool());
};

第二步,我们就可以设计测试场景了。在设计场景之前,我们先看一些Gmock的方法

//   EXPECT_CALL(mock_object, Method(argument-matchers))
//       .With(multi-argument-matchers)
//       .Times(cardinality)
//       .InSequence(sequences)
//       .After(expectations)
//       .WillOnce(action)
//       .WillRepeatedly(action)
//       .RetiresOnSaturation();
//
// where all clauses are optional, and .InSequence()/.After()/
// .WillOnce() can appear any number of times.
  • EXPECT_CALL声明一个调用期待,就是我们期待这个对象的这个方法按什么样的逻辑去执行。
  • mock_object是我们mock的对象,上例中就是TestUser的一个对象。
  • Method是mock对象中的mock方法,它的参数可以通过argument-matchers规则去匹配。
  • With是多个参数的匹配方式指定。
  • Times表示这个方法可以被执行多少次。如果超过这个次数,则按默认值返回了。
  • InSequence用于指定函数执行的顺序。它是通过同一序列中声明期待的顺序确定的。
  • After方法用于指定某个方法只能在另一个方法之后执行。
  • WillOnce表示执行一次方法时,将执行其参数action的方法。一般我们使用Return方法,用于指定一次调用的输出。
  • WillRepeatedly表示一直调用一个方法时,将执行其参数action的方法。需要注意下它和WillOnce的区别,WillOnce是一次,WillRepeatedly是一直。
  • RetiresOnSaturation用于保证期待调用不会被相同的函数的期待所覆盖。

先举一个例子,我们要求Online在第一调用时返回true,之后都返回false。Login一直返回false。Pay一直返回true。也就是说用户第一次支付前处于在线状态,并可以支付成功。而第二次将因为不处于在线状态,要触发登录行为,而登录行为将失败。我们看下这个逻辑该怎么写

    {TestUser test_user;EXPECT_CALL(test_user, Online()).WillOnce(testing::Return(true));EXPECT_CALL(test_user, Login(_,_)).WillRepeatedly(testing::Return(false));EXPECT_CALL(test_user, Pay(_)).WillRepeatedly(testing::Return(true));Biz biz;biz.SetUser(&test_user);std::string admin_ret = biz.pay("user", "", 1);admin_ret = biz.pay("user", "", 1);}

第4行的意思是Online在调用一次后返回true,之后的调用返回默认的false。第5行意思是Login操作一直返回false,其中Login的参数是两个下划线(_),它是通配符,就是对任何输入参数都按之后要求执行。第6行意思是Pay操作总是返回true。那么我们在第10行和第11行分别得到如下输出

login status.pay success.
logout status.login error.

可以见得输出符合我们的预期。

​ 我们再看一种场景,这个场景我们使用了函数参数的过滤。比如我们不允许admin的用户通过我们方法登录并支付,则可以这么写

    {TestUser test_user;EXPECT_CALL(test_user, Online()).WillOnce(testing::Return(false));EXPECT_CALL(test_user, Login("admin",_)).WillRepeatedly(testing::Return(false));Biz biz;biz.SetUser(&test_user);std::string admin_ret = biz.pay("admin", "", 1);}

第3行表示,如果Login的第一个参数是admin,则总是返回false。于是07行返回是

logout status.login error.

那么如果不是admin的用户登录,则返回成功,这个案例要怎么写呢?

    {TestUser test_user;EXPECT_CALL(test_user, Online()).WillOnce(testing::Return(false));EXPECT_CALL(test_user, Login(StrNe("admin"),_)).WillRepeatedly(testing::Return(true));EXPECT_CALL(test_user, Pay(_)).WillRepeatedly(testing::Return(true));Biz biz;biz.SetUser(&test_user);std::string user_ret = biz.pay("user", "", 1);}

03行使用了StrNe的比较函数,即Login的第一个参数不等于admin时,总是返回true。08行的输出是

logout status.login success.pay success.

我们再看一个例子,我们要求非admin用户登录成功后,只能成功支付2次,之后的支付都失败。这个案例可以这么写

    {TestUser test_user;EXPECT_CALL(test_user, Online()).WillOnce(testing::Return(false));EXPECT_CALL(test_user, Login(StrNe("admin"),_)).WillRepeatedly(testing::Return(true));EXPECT_CALL(test_user, Pay(_)).Times(5).WillOnce(testing::Return(true)).WillOnce(testing::Return(true)).WillRepeatedly(testing::Return(false));Biz biz;biz.SetUser(&test_user);std::string user_ret = biz.pay("user", "", 1);user_ret = biz.pay("user", "", 1);user_ret = biz.pay("user", "", 1);}

第4行我们使用Times函数,它的参数5表示该函数期待被调用5次,从第6次的调用开始,返回默认值。Times函数后面跟着两个WillOnce,其行为都是返回true。这个可以解读为第一次和第二次调用Pay方法时,返回成功。最后的WillRepeatedly表示之后的对Pay的调用都返回false。我们看下执行的结果

logout status.login success.pay success.
logout status.login success.pay success.
logout status.login success.pay error.

从结果上看,前两次都支付成功了,而第三次失败。符合我们的期待。

相关文章:

C++单元测试GoogleTest和GoogleMock十分钟快速上手(gtestgmock)

C单元测试GoogleTest和GoogleMock(gtest&gmock) 环境准备 下载 git clone https://github.com/google/googletest.git # 或者 wget https://github.com/google/googletest/releases/tag/release-1.11.0安装 cd googletest cmake CMakeLists.txt make sudo make instal…...

Starknet的去中心化路线图

1. 引言 StarkWare正以2条路线在迈向去中心化&#xff1a; planningimplementation 以让Starknet协议 走向 去中心化proof-of-stake协议。 Starknet向以太坊发送STARK proofs来验证其状态变更。 一年前Starknet就在做去中心化规划&#xff0c;相关提案见&#xff1a; Sim…...

python基础语法(十二)

目录 标准库认识标准库使用 import 导入模块代码示例: 字符串操作剑指offer 58, 翻转单词顺序题目题目做法代码 leetcode 796, 旋转字符串题目题目做法 leetcode 2255, 统计是给定字符串前缀的字符串数目题目题目做法 代码示例: 文件查找工具 感谢各位大佬对我的支持,如果我的文…...

【开源】基于SpringBoot的农村物流配送系统的设计和实现

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统登录、注册界面2.2 系统功能2.2.1 快递信息管理&#xff1a;2.2.2 位置信息管理&#xff1a;2.2.3 配送人员分配&#xff1a;2.2.4 路线规划&#xff1a;2.2.5 个人中心&#xff1a;2.2.6 退换快递处理&#xff1a;…...

【2024秋招】2023-9-16 贝壳后端开发一面

1 秒杀系统 1.1 如何抗住高并发 1.2 数据一致性你是怎么处理&#xff0c;根据场景来说明你的设计思路 1.3 你们当时系统的架构是怎么样的 秒杀表做节点隔离&#xff0c; 1.4 为了保证数据一致性&#xff0c;引入了redission的锁&#xff0c;你是为了抗住高并发而去为了引入…...

BI是什么?想要了解BI需要从哪些方面入手?

企业为了执行数字化战略&#xff0c;实行数字化转型&#xff0c;实现数据价值&#xff0c;除了需要相关数字化技术及理念、人才等&#xff0c;还需要借助数字化相关应用&#xff0c;例如商业世界中广受企业欢迎的ERP、OA、CRM等业务信息系统&#xff0c;以及上升势头非常迅猛的…...

软件测试---等价类划分(功能测试)

能对穷举场景设计测试点-----等价类划分 等价类划分 说明&#xff1a;在所有测试数据中&#xff0c;具有某种共同特征的数据集合进行划分分类&#xff1a; 1&#xff09;有效等价类 2&#xff09;无效等价类步骤&#xff1a;1&#xff09;明确需求 2&#xff09;确定有效和无…...

javascript原生态xhr上传多个图片,可预览和修改上传图片为固定尺寸比例,防恶意代码,加后端php处理图片

//前端上传文件 <!DOCTYPE html> <html xmlns"http://www.w3.org/1999/xhtml" lang"UTF-8"></html> <html><head><meta http-equiv"Content-Type" content"text/html;charsetUTF-8;"/><title…...

【Java】Map集合中常用方法

Map集合的常用方法 方法名称作用V put(Key k,V value)添加元素V remove(K key, V value)根据键值删除对应的值void clear()清除所有键值元素boolean containsKey(Object key)判断集合中是否包含指定的键boolean containsValue(Object value)判断集合中是否包含指定的值boolean …...

方太描画未来厨房的模样

作者 | 辰纹 来源 | 洞见新研社 不知不觉中&#xff0c;iPhone已经更新到15代了&#xff0c;家里的电视变成了越来越轻薄的液晶屏&#xff0c;过去被称为“老三样”的富康&#xff0c;捷达、桑塔纳&#xff0c;如今也被以特斯拉为代表的新能源智能汽车们所取代…… 类似以上的…...

ELASTICO-A Secure Sharding Protocol For Open Blockchains

INTRO 在中本聪共识中&#xff0c;通过POW机制来公平的选举leader&#xff0c;不仅非常消耗power&#xff0c;并且拓展性也不好。现在比特币中是7 TPS&#xff0c;和其他的支付系统相比效率相差甚远。 当前的许多拜占庭共识协议&#xff0c;并不支持在一个开放的环境中使用&a…...

【数据结构】Map和Set

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;浅谈数据结构 &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; Map、Set 1. 搜索树1.1 概念1.2 性能…...

Python Flask

Python Flask是一个轻量级的web开发框架&#xff0c;用于快速地构建web应用程序。以下是Python Flask的基本使用步骤&#xff1a; 安装Flask&#xff1a;使用pip安装Flask包。在命令行中输入以下命令&#xff1a; pip install flask创建Flask对象&#xff1a;在Python文件中&am…...

时序预测 | Python实现ARIMA-LSTM差分自回归移动平均模型结合长短期记忆神经网络时间序列预测

时序预测 | Python实现ARIMA-LSTM差分自回归移动平均模型结合长短期记忆神经网络时间序列预测 目录 时序预测 | Python实现ARIMA-LSTM差分自回归移动平均模型结合长短期记忆神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 时序预测 | Python实现ARIM…...

Redis快速上手篇八(redission完善分布式锁)

Redisson Redisson是一个在Redis的基础上实现的Java驻内存数据网格&#xff08;In-Memory Data Grid&#xff09;。它不仅提供了一系列的分布式的Java常用对象&#xff0c;还提供了许多分布式服务&#xff0c;其中就包含了各种分布式锁的实现。简单说就是redis在分布式系统上工…...

Dataset文件下载以及使用,以nuswide为例

文章目录 文件夹结构如何使用继承torch.utils.data.Dataset构建新的class构建新的Dataloader 数据集要求以文章 multi-label learning from single positive label为例&#xff1b; 文件夹结构 我是这么放置的&#xff0c;其中含有obs的文件是通过运行文件夹preproc下的genera…...

ZYNQ连载02-开发环境

ZYNQ连载02-开发环境 1. 官方文档 ZYNQ开发使用的软件为Vivado/Vitis/PetaLinux,软件体积比较大&#xff0c;硬盘保留100G以上的空间&#xff0c;赛灵思提供详细的文档&#xff0c;链接如下&#xff1a; ZYNQ文档 2. Vivido和Vitis安装 赛灵思统一安装程序 3. PetaLinux安装…...

前端 :用HTML和css制作一个小米官网的静态页面

1.HTML&#xff1a; <body><div id "content"><div id "box"><div id "top"><div id "top-left"><span id "logo">MI</span><span id "text-logo">小米账…...

modelsim仿真报错:vlog-2388 ‘scl‘ already declared in this scope

问题背景&#xff1a; 1、使用vivado直接仿真的时候没有报错。 2、在vivado中调用modelsim的时候报错。 报错的代码&#xff1a; module iic_write(input clk,input rst,output scl,input en,inout sda);reg scl&#xff1b;……报错的意思是scl已经声明过了&#xff0c;mode…...

C#中通过BeginInvoke()和EndInvoke()来实现异步

.NET Framework允许异步调用任何方法。定义与需要调用的方法具有相同签名的委托&#xff1b;公共语言运行库将自动为该委托定义具有适当签名的 BeginInvoke 和 EndInvoke 方法。以下介绍C#中&#xff0c;通过BeginInvoke()和EndInvoke()来实现异步。 1、异步编程 调用BeginInv…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器

一.自适应梯度算法Adagrad概述 Adagrad&#xff08;Adaptive Gradient Algorithm&#xff09;是一种自适应学习率的优化算法&#xff0c;由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率&#xff0c;适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

云原生安全实战:API网关Kong的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关&#xff08;API Gateway&#xff09; API网关是微服务架构中的核心组件&#xff0c;负责统一管理所有API的流量入口。它像一座…...

搭建DNS域名解析服务器(正向解析资源文件)

正向解析资源文件 1&#xff09;准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2&#xff09;服务端安装软件&#xff1a;bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)

引言 工欲善其事&#xff0c;必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后&#xff0c;我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集&#xff0c;就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

tomcat入门

1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效&#xff0c;稳定&#xff0c;易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...

wpf在image控件上快速显示内存图像

wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像&#xff08;比如分辨率3000*3000的图像&#xff09;的办法&#xff0c;尤其是想把内存中的裸数据&#xff08;只有图像的数据&#xff0c;不包…...

Python网页自动化Selenium中文文档

1. 安装 1.1. 安装 Selenium Python bindings 提供了一个简单的API&#xff0c;让你使用Selenium WebDriver来编写功能/校验测试。 通过Selenium Python的API&#xff0c;你可以非常直观的使用Selenium WebDriver的所有功能。 Selenium Python bindings 使用非常简洁方便的A…...