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
中的断言分成两大类:
-
ASSERT_\*
系列:如果检测失败就直接退出当前函数 -
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条路线在迈向去中心化: planningimplementation 以让Starknet协议 走向 去中心化proof-of-stake协议。 Starknet向以太坊发送STARK proofs来验证其状态变更。 一年前Starknet就在做去中心化规划,相关提案见: Sim…...
python基础语法(十二)
目录 标准库认识标准库使用 import 导入模块代码示例: 字符串操作剑指offer 58, 翻转单词顺序题目题目做法代码 leetcode 796, 旋转字符串题目题目做法 leetcode 2255, 统计是给定字符串前缀的字符串数目题目题目做法 代码示例: 文件查找工具 感谢各位大佬对我的支持,如果我的文…...

【开源】基于SpringBoot的农村物流配送系统的设计和实现
目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统登录、注册界面2.2 系统功能2.2.1 快递信息管理:2.2.2 位置信息管理:2.2.3 配送人员分配:2.2.4 路线规划:2.2.5 个人中心:2.2.6 退换快递处理:…...
【2024秋招】2023-9-16 贝壳后端开发一面
1 秒杀系统 1.1 如何抗住高并发 1.2 数据一致性你是怎么处理,根据场景来说明你的设计思路 1.3 你们当时系统的架构是怎么样的 秒杀表做节点隔离, 1.4 为了保证数据一致性,引入了redission的锁,你是为了抗住高并发而去为了引入…...

BI是什么?想要了解BI需要从哪些方面入手?
企业为了执行数字化战略,实行数字化转型,实现数据价值,除了需要相关数字化技术及理念、人才等,还需要借助数字化相关应用,例如商业世界中广受企业欢迎的ERP、OA、CRM等业务信息系统,以及上升势头非常迅猛的…...

软件测试---等价类划分(功能测试)
能对穷举场景设计测试点-----等价类划分 等价类划分 说明:在所有测试数据中,具有某种共同特征的数据集合进行划分分类: 1)有效等价类 2)无效等价类步骤:1)明确需求 2)确定有效和无…...

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 …...

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

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

【数据结构】Map和Set
⭐ 作者:小胡_不糊涂 🌱 作者主页:小胡_不糊涂的个人主页 📀 收录专栏:浅谈数据结构 💖 持续更文,关注博主少走弯路,谢谢大家支持 💖 Map、Set 1. 搜索树1.1 概念1.2 性能…...
Python Flask
Python Flask是一个轻量级的web开发框架,用于快速地构建web应用程序。以下是Python Flask的基本使用步骤: 安装Flask:使用pip安装Flask包。在命令行中输入以下命令: pip install flask创建Flask对象:在Python文件中&am…...

时序预测 | Python实现ARIMA-LSTM差分自回归移动平均模型结合长短期记忆神经网络时间序列预测
时序预测 | Python实现ARIMA-LSTM差分自回归移动平均模型结合长短期记忆神经网络时间序列预测 目录 时序预测 | Python实现ARIMA-LSTM差分自回归移动平均模型结合长短期记忆神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 时序预测 | Python实现ARIM…...
Redis快速上手篇八(redission完善分布式锁)
Redisson Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。简单说就是redis在分布式系统上工…...

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

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

前端 :用HTML和css制作一个小米官网的静态页面
1.HTML: <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
问题背景: 1、使用vivado直接仿真的时候没有报错。 2、在vivado中调用modelsim的时候报错。 报错的代码: module iic_write(input clk,input rst,output scl,input en,inout sda);reg scl;……报错的意思是scl已经声明过了,mode…...
C#中通过BeginInvoke()和EndInvoke()来实现异步
.NET Framework允许异步调用任何方法。定义与需要调用的方法具有相同签名的委托;公共语言运行库将自动为该委托定义具有适当签名的 BeginInvoke 和 EndInvoke 方法。以下介绍C#中,通过BeginInvoke()和EndInvoke()来实现异步。 1、异步编程 调用BeginInv…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...

FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...