GoogleTest : 测试框架(单元测试)
GoogleTest
googletest: GoogleTest - Google Testing and Mocking Framework
googletest 是一个由 Google 的测试技术团队开发的测试框架,它考虑到了谷歌的特定需求和限制。无论你使用的是 Linux、Windows 还是 Mac,只要你编写 C++ 代码,googletest 都可以帮到你。它支持任何类型的测试,不只是单元测试。
-
独立的且可重复的(googletest 通过在不同的对象上运行每个测试用例来隔离测试。当测试失败时允许你单独运行它,以便快速调试);
-
可移植的且可复用的(googletest 适用于不同的操作系统,不同的编译器);
-
失败时,提供尽可能多的关于故障的信息(googletest 不会在第一个测试失败时停止。相反,它仅停止当前的测试并继续运行下一个);
-
得到良好的组织并反映测试代码的结构(googletest 将相关的测试分组为测试套件,它们可以共享数据和子例程);
-
测试框架将精力集中在测试内容上(googletest 自动追踪所有定义的测试,且无需用户以运行它们的顺序迭代它们);
-
快(通过 googletest,可以跨测试用例复用共享资源,且只支出一次 set-up/tear-down 的开销,不使测试相互依赖)。
任何可被送给
ostream
的都可以被送进断言的宏。
编译
gtest 提供了两种编译方式 bazel 和 cmake,以 cmake 为例:
mkdir build && cd build
cmake ..
# 只需要包含头文件 gtest.h或者gmock.h
断言
断言是 gtest 核心组成,所有的测试用例最终都是以断言实现的。
-
ASSERT 系列的断言如果失败,则会产生一个严重错误并导致当前作用范围的测试用例中断;
-
EXPECT 不会产生任何错误,测试用例依旧可以继续执行。
true / false 条件测试
致命 | 非致命 | 验证 |
---|---|---|
ASSERT_TRUE(condition); | EXPECT_TRUE(condition); | condition: true |
ASSERT_FALSE(condition); | EXPECT_FALSE(condition); | condition: false |
失败时,
ASSERT_*
产生一个致命错误,并从当前函数退出,EXPECT_*
则产生一个非致命错误,并允许函数继续运行。
二元比较
致命断言 | 非致命断言 | 验证 |
---|---|---|
ASSERT_EQ(val1, val2); | EXPECT_EQ(val1, val2); | val1 == val2 |
ASSERT_NE(val1, val2); | EXPECT_NE(val1, val2); | val1 != val2 |
ASSERT_LT(val1, val2); | EXPECT_LT(val1, val2); | val1 < val2 |
ASSERT_LE(val1, val2); | EXPECT_LE(val1, val2); | val1 <= val2 |
ASSERT_GT(val1, val2); | EXPECT_GT(val1, val2); | val1 > val2 |
ASSERT_GE(val1, val2); | EXPECT_GE(val1, val2); | val1 >= val2 |
比较 C 字符串,ASSERT_EQ 测试它们是否位于相同内存位置,而不是是否具有相同的值。因此,比较 C 字符串的值使用
ASSERT_STREQ()
;当执行指针比较时使用
*_EQ(ptr, nullptr)
和*_NE(ptr, nullptr)
而不是*_EQ(ptr, NULL)
和*_NE(ptr, NULL)
。
字符串比较
致命断言 | 非致命断言 | 验证 |
---|---|---|
ASSERT_STREQ(str1, str2); | EXPECT_STREQ(str1, str2); | 具有相同内容 |
ASSERT_STRNE(str1, str2); | EXPECT_STRNE(str1, str2); | 具有不同内容 |
ASSERT_STRCASEEQ(str1, str2); | EXPECT_STRCASEEQ(str1, str2); | 相同内容忽略大小写 |
ASSERT_STRCASENE(str1, str2); | EXPECT_STRCASENE(str1, str2); | 不同内容忽略大小写 |
如果宽字符串(
wchar_t*
, Windows 上UNICODE
模式的TCHAR*
,或std::wstring
)被送进断言,它将在打印时被转换为 UTF-8。
断言扩展
SUCCEED()
: 产生一个成功标识,只代表某一个步躁成功;
FAIL()
: 产生一个严重错误,相当于一个 ASSERT 宏失败;
ASSERT_THROW(expre,type)
: 断言表达式 expre 会抛出一个 type 类型异常;
ASSERTANY THROW(expre)
: 断言表达式 expre 会抛出一个任意类型异常;
ASSERT_NO_THROW(expre)
: 断言表达式 expre 不会抛出任何异常。
测试 TEST
-
使用 TEST() 宏定义并命名一个测试函数(没有返回值的宏函数);
-
解析命令行中的 GoogleTest 参数,它允许用户通过多样的命令行参数来控制测试程序的行为(即定制命令行参数的行为):
testing::InitGoogleTest(&argc, argv);
; -
搜索不同的 Test Case 和不同的源文件中所有已经存在的测试案例然后运行它们,成功时返回 1,否则返回 0:
return RUN_ALL_TESTS();
。
// 1. TestSuiteName 测试套件名
// 2. TestName 测试用例名
/// 名称不该包含下划线
TEST(FactorialTest, HandlesZeroInput) {EXPECT_EQ(Factorial(0), 1);
}
TEST(FactorialTest, HandlesPositiveInput) {EXPECT_NE(Factorial(1), 1);
} int main(int argc, char** argv) {testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
Google C++ Style Guide 函数和类命名规则
全局事件
#include "gtest/gtest.h"
class TestFA2 : public testing::Environment {
public:void SetUp() override {std::cout << "SetUp" << std::endl;}void TearDown() override {std::cout << "TearDown" << std::endl;}
};
TEST(TestFA21, globalEnv) {EXPECT_EQ(3, 4) << "int compare";
}
int main(int argc, char** argv) {testing::InitGoogleTest(&argc, argv);testing::Environment* env = new TestFA2();testing::AddGlobalTestEnvironment(env);int ret = 0;ret = RUN_ALL_TESTS();return ret;
}
测试夹具 TEST_F
-
允许你为多个不同的测试复用相同的数据对象配置。
-
googletest 不 为多个测试复用相同的测试夹具。
成员函数
虚函数
- virtual void SetUp():类似于构造函数,在 TEST_F 之前运行;
- virtual void TearDown():类似于析构函数,在 TEST_F 之后运行。
静态函数
- static void SetUpTestSuite():在第一个 TEST 之前运行;
- static void TearDownTestSuite():在最后一个 TEST 之后运行。
全局事件
继承 testing::Environment
- virtual void SetUp():在所有用例之前运行;
- virtual void TearDown():在所有用例之后运行。
创建
-
创建一个公有继承自 ::testing::Test 的类,进行 protected 类内部声明;
-
编写一个默认的构造函数或 SetUp() 函数为每个 test 准备对象;
-
编写一个析构函数或 TearDown() 函数释放 SetUp() 中分配的资源;
-
完善类资源信息,使用 TEST_F 宏,第一个参数填写新建夹具类名称。
由于 C++ 语法不允许在构造/析构函数中调用虚函数 SetUp/TearDown,建议优先使用构造/析构函数完成数据的初始化。
使用
- 使用 TEST_F(类名, 测试夹具名称) :
// TestFixtureName 测试夹具类名(_F:fixture)
TEST_F(TestFixtureName, TestName) {... test body ...
}
通过 TEST_F() 定义的每个测试,googletest 将在运行时自动创建一个 全新的 测试夹具,立即通过 SetUp() 初始化它,运行测试,最后调用 TearDown() 清理资源,然后删除测试夹具。
具体类定义
template < typename E > // E is the element type.
class Queue {
public:Queue();void Enqueue(const E& element);E* Dequeue(); // Returns NULL if the queue is empty.size_t size() const;...
};
夹具类定义
#include "gtest/gtest.h"
class TestFA : public ::testing::Test {
public:TestFA() {}~TestFA() {}protected:void SetUp() override {std::cout << "Reference count: " << deq.use_count() << std::endl;auto p = deq.get();auto pxin = *p;std::deque< int > a(5, -1);std::cout << "1. size: " << pxin.size() << std::endl;pxin.swap(a);std::cout << "2. size:" << pxin.size() << std::endl;}void TearDown() override {deq.reset();deq = nullptr;}public:std::shared_ptr< std::deque< int > > deq =std::make_shared< std::deque< int > >();
};
测试定义
- 测试案例都来自同一个测试夹具;
- 不同的测试案例,拥有相互独立的个体,独占数据,不会相互影响。
TEST_F(TestFA, isEmptyOnB) {EXPECT_GE(deq.use_count(), 1);if (deq.use_count() != 0) {deq.reset();}
}
TEST_F(TestFA, isEmptyOnBIndependence) {EXPECT_GE(deq.use_count(), 1);
}
测试调用
定义测试后,通过 RUN_ALL_TESTS() 运行它们,如果所有测试都成功,返回 0,否则返回 1。
调用 RUN_ALL_TESTS() 宏时:
-
保存所有 googletest 标记的状态;
-
为第一个测试创建一个测试夹具对象;
-
通过 SetUp() 初始化;
-
在测试夹具对象上运行测试;
-
通过 TearDown() 清理测试夹具;
-
删除夹具,恢复所有的 googletest 标记的状态;
-
为下一个测试重复上述步骤。
#include "gtest/gtest.h"
int main(int argc, char** argv) {// 解析 googletest 标记的命令行参数,并移除所有已识别的标记testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
调用 RUN_ALL_TESTS() 一次,多次调用将产生冲突。
接口测试 Gmock
Google C++单元测试框架GoogleTest—Google Mock简介–概念及基础语法 - 超超boy - 博客园
使用步骤
创建抽象类
- 创建抽象接口函数;
class B {
public:virtual const int sub(int s1, const int s2) const = 0;
};
创建 Mock 类继承
- 类返回作用域、参数作用域等,都需要进行描述;
#include "gmock/gmock.h"
class MockB : public B {
public:MOCK_METHOD(const int, sub, ( int, const int ), (const, override));
};
Mock 测试
- EXPECT_CALL(mock_object, method(matchers)) // 对象、匹配调用函数
.Times(cardinality) // 调用次数
.WillOnce(action) // 被调用一次时的行为
.WillRepeatedly(action); // 重复调用
#include "B.h"
#include "gtest/gtest.h"
MockB b;
TEST(mock_sub, actionBSub) {EXPECT_CALL(b, sub).Times(::testing::AtLeast(3)) // 3次需要3个WillOnce.WillOnce(::testing::Return(1)).WillOnce(::testing::Return(2)).WillOnce(::testing::Return(34));std::cout << b.sub(2, 1) << std::endl;std::cout << b.sub(3, 1) << std::endl;std::cout << b.sub(55, 44) << std::endl;std::cout << b.sub(34, 0) << std::endl; // 默认返回 0
} // 参数匹配器
TEST(mock_sub, expectCall) {EXPECT_CALL(b, sub(123, 123)).WillRepeatedly(Return(246));std::cout << b.sub(123, 1234) << std::endl; // 默认 0std::cout << b.sub(123, 123) << std::endl; // 符合,246,测试用例失效std::cout << b.sub(123, 123) << std::endl; // 默认 0EXPECT_CALL(b, sub(Gt(123), _)).WillOnce(Return(1234)); // 大于 123,和任意数值std::cout << b.sub(124, 123) << std::endl; // 符合,-99std::cout << b.sub(123, 2) << std::endl; // 0
} // 顺序适配器
TEST(mock_multi_sub, callInSequence) {Sequence s1;EXPECT_CALL(b, multi).InSequence(s1).WillOnce(Return(222)); // 先EXPECT_CALL(b, sub).InSequence(s1).WillOnce(Return(333)); // 后std::cout << b.sub(124, 123) << std::endl; // 不符。为 0std::cout << b.multi(123, 2) << std::endl; // 先,222std::cout << b.sub(124, 123) << std::endl; // 后,333
}
EXPECT_CALL
- Times :至少调用次数,小于则默认回调,如默认 int 返回 0;
- WillRepeatedly :重复调用,类似默认调用;
- ::testing::Gt(a) :函数参数匹配,设置匹配器,如当大于 a 时则匹配调用;
- InSequence :指定顺序适配器 Sequence 顺序调用成员函数;
示例
示例1:web 请求
std::string chat_room::log() {std::string* response;this->requester->execute("request",response); // web访问,结果存在response指针中return *response;
} class http_request {
public:virtual ~http_request() {}virtual bool execute(std::string request, std::string* response) = 0;
};
class mock_http_request : public http_request {
public:MOCK_METHOD(bool, execute, (std::string request, std::string* response),(override));
}; TEST(ChatRoomTest, log) {testing::NiceMock< mock_message_dao >mock_dao; // 在下一部分会提到mock_message_daomock_http_request mock_requester; // Mock对象std::string response ="response"; // 期待调用函数的第二个参数将指向这个string对象EXPECT_CALL(mock_requester, execute).WillRepeatedly( // 每次调用都会(WillRepeatedly)执行testing::DoAll( // 每次执行包含多个行为testing::SetArgPointee< 1 >(response), // 将传入参数指针变量response指向responsetesting::Return(true))); // 返回值为truechat_room cr = chat_room(&mock_dao, &mock_requester); // 将mock对象通过chat_room的constructor注入EXPECT_EQ(cr.log(), "response"); // 调用和Google Test断言
}
示例2:数据库访问
void chat_room::join(chat_participant_ptr participant) {participants_.insert(participant);std::vector< std::string > recent_msg_strs =this->dao->get_messages(); // 从数据库中获取历史消息for (std::string recent_msg_str : recent_msg_strs) {// 将每一个消息发送给该聊天参与者auto msg = chat_message();msg.set_body_string(recent_msg_str);participant->deliver(msg);}
} class message_dao {
public:virtual ~message_dao() {}virtual bool add_message(std::string m) = 0;virtual std::vector< std::string > get_messages() = 0;
}; class mock_message_dao : public message_dao {
public:MOCK_METHOD(bool, add_message, (std::string m), (override));MOCK_METHOD(std::vector< std::string >, get_messages, (), (override));
};
EST(ChatRoomTest, join) {mock_message_dao mock_dao; // 创建mock对象(需要注入chat_room)http_request_impl requester; // 创建web访问对象(也需要注入chat_room)auto mock_p1 = std::make_shared< mock_chat_participant >();// 创建participant的mock指针EXPECT_CALL(mock_dao, get_messages).WillOnce(testing::Return(std::vector< std::string >{ "test_1", "test_2", "test_3" }));// 指定get_messages调用的返回值EXPECT_CALL(*mock_p1, deliver).Times(3);// 指定deliver调用的次数chat_room cr = chat_room(&mock_dao, &requester);// 创建chat_room对象,注入dao和requestercr.join(mock_p1); // 调用
}
注意
-
测试套件名称、测试夹具名称、测试名称中不应该出现下划线( TEST(TestSuiteName, TestName),生成名为TestSuiteName_TestName_Test 的类);
-
Uniteresting mock function call 警告 : 没有相应匹配的EXCEPT_CALL,Google Mock 会生成这个警告,使用 NiceMock 进行 mock 类对象实例化;
-
Mock 一般用于虚函数,通过 TMock 来 Mock 非虚函数;
-
单元测试文件可以放在根目录下面专用的 tests 文件夹下;
相关文章:

GoogleTest : 测试框架(单元测试)
GoogleTest googletest: GoogleTest - Google Testing and Mocking Framework googletest 是一个由 Google 的测试技术团队开发的测试框架,它考虑到了谷歌的特定需求和限制。无论你使用的是 Linux、Windows 还是 Mac,只要你编写 C 代码,goo…...

商业银行财富管理“智能原生”能力呈阶梯化,AI助力商业模式趋向多元化发展
易观:金融业的财富管理从经营角度来看,是“客户与渠道管理场景运营产品研发”三位一体共同构建以客户为中心,数据驱动的业务经营体系。其中,“客户与渠道管理”是将客户利益作为核心目标,通过升级用户体验、客户全生命…...

2022年中国云市场份额:阿里云腾讯云下降
我是卢松松,点点上面的头像,欢迎关注我哦! 4月23日,IDC发布《中国公有云服务市场(2022下半年)跟踪》,占据前四的分别为阿里云(40.6%)、华为云(11.0%)、腾讯云(11.0%)、中国电信(8.7%)。咱们说重点,如下图所…...

Redis入门学习笔记【二】Redis缓存
目录 一、Redis缓存 二、Redis使用缓存遇到的问题 2.1 数据一致性 2.2缓存雪崩 2.3 缓存穿透 2.4 缓存击穿 一、Redis缓存 数据缓存是Redis最重要的一个场景,为缓存而生,在springboot中,一般有两种使用方式: 直接通过RedisT…...

go1.20环境安装以及beego框架配置
打开网址下载安装包选择对应安装包来下载安装(个人是windows,下载的1.20.3版本) 默认情况下会安装在C盘,但是我安装在了D盘目录 根据安装提示一步步next,直至完成 go get 在1.18版本之后就弃掉了,换成了install 配置自己的work…...

vue---组件逻辑复用方法:Mixin/HOC/Renderless组件
目录 1、Mixin 2、HOC 3、Renderless组件 下文通过表单校验来分别讲解Mixin/HOC/Renderless组件这三种方式。 1、Mixin 通过mixin将一个公用的validate函数同步到每一个组件中去 mixin使用详细介绍见:vue---mixin混入_maidu_xbd的博客-CSDN博客一个混入对象可…...

阳光万里,祝你上岸——免统考在职研究生
什么是在职研究生 在职研究生,是国家计划内,以在职人员身份,部分时间在职工作,部分时间在校学习的研究生教育的一种类型。在职攻读硕士方式有三种: 1.双证非全日制研究生:为普通高等教育研究生学历&#x…...

大孔树脂型号,A-722,ADS500,ADS600,ADS750,ADS800
一、产品介绍 基于吸附功能的聚苯乙烯特种树脂 Tulsimer ADS-600 是一款没有离子官能基的,由交联聚苯乙烯合成的功能强大的吸附型树脂。 Tulsimer ADS-600 主要应用于水溶液中吸附酚及其化合物,氯代烃等含氯物质,表面活性剂࿰…...

MATLAB在逐渐被Python淘汰吗?
Python和MATLAB都是常用的科学计算工具,但是它们有很多不同之处。 Python是一种通用编程语言,而MATLAB主要是用来做数值计算的。Python的基本数据类型和一般的编程语言一样普遍,但是离开了Numpy这个包,就不再有数组或者矩阵的数据…...

黑盒测试过程中【测试方法】讲解1-等价类,边界值,判定表
在黑盒测试过程中,有9种常用的方法:1.等价类划分 2.边界值分析 3.判定表法 4.正交实验法 5.流程图分析 6.因果图法 7.输入域覆盖法 8.输出域覆盖法 9.猜错法 我们一般用第1种和第2种方法最多。此处简单介绍一下这两种方法,详细介绍其…...

函数栈帧的创建和销毁
文章目录 main函数栈帧的创建为什么局部变量的值是随机值函数是怎么传参的?传参的顺序是什么?函数调用结束是怎么返回的 main函数栈帧的创建 先在栈帧里面为main函数开辟出一块空间,且这块空间由两个寄存器esp和ebp来进行维护,当在调用另一个函数时这两个寄存器就去维护为这…...

测试Ocr工具IronOCR(续:编写图片圈选程序)
上一篇文章学习了IronOCR的基本用法之后,计划做一个加载本地图片后,从图片中圈选某一位置的文字,然后调用IronOCR识别圈选区域文本的程序。本文实现从本地加载图片并完成圈选的功能。 主要的功能包括以下几点: 1ÿ…...

React之Redux的使用
文章目录 Redux 介绍概述Redux 是什么?为什么要使用 Redux?我什么时候应该使用 Redux?Redux 库和工具React-ReduxRedux ToolkitRedux DevTools 扩展 Redux 术语和概念State 管理不可变性 Immutability术语ActionAction CreatorReducerReducer…...

数据库系统概论--第五章课后习题
1.什么是数据库的完整性? 答:数据库的完整性是指数据的正确性和相容性。 2. 数据库的完整性概念与数据库的安全性概念有什么区别和联系? 答: 数据的完整性和安全性是两个不同的概念,但是有一定的联系。前者是为了防止数据库中存…...

小程序开发费用估算:如何控制项目成本?
在当今数字化的时代,小程序已经成为了很多企业和个人开展业务的重要手段。小程序的开发需要耗费时间和资源,因此在项目初期,了解预计的开发费用是非常重要的。本文将详细介绍如何估算小程序开发费用以及如何控制项目成本。 小程序开发费用 …...

【22】linux进阶——文本处理工具:cut、awk、sed
大家好,这里是天亮之前ict,本人网络工程大三在读小学生,拥有锐捷的ie和红帽的ce认证。每天更新一个linux进阶的小知识,希望能提高自己的技术的同时,也可以帮助到大家 另外其它专栏请关注: 锐捷数通实验&…...

Web3D包装生产线 HTML5+Threejs(webgl)开发
生产线三维可视化解决方案就是通过物联网、虚实联动和三维建模等先进技术,以一个3D立体模型展现出来,可以让我们很直观的看到生产线的运作以及对数据的监控。3D运用数据孪生技术可以让工业3D物联网管理系统的界面变得非常的简单易看,并且能够…...

Docker数据管理
目录 一、数据管理方式 1、数据卷 在宿主机上写入数据,传到容器 2、数据卷容器 二、容器互联 一、数据管理方式 用户在使用Docker的过程中,往往需要能查看容器内应用产生的数据,或者需要把容器内的数据进行备份,甚至多…...

在PowerBuilder中更改窗口继承
在PowerBuilder中更改窗口继承 PowerBuilder是Sybase公司的独资子公司PowerSoft推出的应用于客户机/服务器体系结构下的应用程序开发工具,也是一种面向对象的图形化交互式开发工具。PowerBuilder 以其简洁高效的集成开发环境、 强大的数据窗口技术、几乎无所不能的数据库访问…...

CS:APP 第7章链接分步编译(cpp/cc1/as/ld)遇到的问题
环境 WSL Ubuntu 22.04.2 LTS gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0 问题 问题一 cc1 命令找不到 cc1 命令在 /usr/lib/gcc/x86_64-linux-gnu/11/cc1 里,注意不同操作系统等可能 cc1 的位置不一样,可以使用 find 或者 locate 命令搜索。 通过下…...

Jsp+sql智能道路交通信息管理系统的设计与实现(论文+系统+开题报告+答辩PPT+外文翻译)
伴随着社会经济的发展,交通在人们经济和社会活动中扮演着越来越重要的角色。因而,交通管理的水平和质量就和我们的日产生活紧密相连,并且直接影响着投资的环境和城市的面貌。因此,每个城市都做了很多关于改善交通和发展智能交通系…...

自动化工具 WEB 自动化工具
背景 使用自动化测试框架编写用例的时候,维护元素信息以及脚本较为麻烦。对应新手来说,编写脚本的能力有限,使用工具会更容易入手。最重要的是可视化操作让我觉得体验舒服。 演示地址 地址:hippo 账号:test 密码&am…...

AutoGPT 安装指南,使用避坑要点
最近, AIGC 中最火的可能就当属于 AutoGPT 了吧,首先简单介绍一下AutoGPT 背景 AutoGPT 是基于 ChatGPT API 接口开发,项目首推 GPT-4 模型,但 OpenAI 账号 API 只有 gpt-3.5-turo 权限同样也可以使用。 项目在 github 上获取的…...

低调且强大——JNPF低代码平台
前言 先说说很多人为什么要用低代码平台。大部分的反馈是“懒”,其次就是后台管理这东西吧,来来回回就那些东西,查询/表单/表格/弹窗之类的,加上一些增删改查的逻辑。很多人表示,呆过的公司好几家了,后管的…...

《商用密码应用与安全性评估》第二章政策法规2.4商用密码应用安全性评估工作
商用密码应用安全性评估体系发展历程 第一阶段:制度奠基期(2007年11月至2016年8月) 第二阶段:再次集结期(2016年9月至2017年4月) 第三阶段:体系建设期(2017年5月至2017年9月&…...

MySQL 字段为 NULL 的5大坑,99%人踩过
数据库字段允许空值(null)的问题,你遇到过吗? 在验证问题之前,我们先建一张测试表及测试数据。 数据库字段允许空值(null)的问题,你遇到过吗? 在验证问题之前,我们先建一张测试表及测试数据。 构建的测试…...

深入理解计算机系统--理解编译器编译的过程
前言 大家在学习C语言的时候,相信对编译器这个词并不会感到陌生。我们也会知道编译器编译的过程是:预处理-》编译-》汇编-》链接。这篇文章主要介绍这四个过程中,编译器究竟做了那些工作,它是如何让一份高级程序转换成机器语言的…...

实现PXE批量网络装机及kickstrat无人值守安装(富士山终究留不住欲落的樱花)
一、PXE概述和部署PXE批量装机 1.PXE简介 PXE(预启动执行环境,在操作系统之前运行)是由Intel公司开发的网络引导技术,c/s架构,允许客户机通过网络从远程服务器下载引导镜像,并加载安装文件或者整个操作系统…...

4.23日报
MySQL 索引是怎么实现的? 索引是满足某种特定查找算法的数据结构,而这些数据结构会以某种方式指向数据,从而实现高效查找数据。 具体来说 MySQL 中的索引,不同的数据引擎实现有所不同,但目前主流的数据库引擎的索引都…...

【数据治理】数据治理方面的证书有哪些?
目前数据治理领域最专业的职业认证有: 中文版: ①DAMA国际CDGA数据治理工程师、DAMA国际CDGP数据治理专家 (先获得CDGA认证才能申请CDGP认证考试) ②DGSP数据治理与管理认证 英文版: ③CDMP数据管理专业人士 一、…...