手把手教你使用gtest写单元测试

2023-11-15

开源框架:gtest,它主要用于写单元测试,检查真自己的程序是否符合预期行为。这不是QA(测试工程师)才学的,也是每个优秀后端开发codoer的必备技能。

本期博文内容及使用的demo,参考:

  • Googletest Basic Guide[1]

  • Googletest Samples [2]

构建依赖环境

按照惯例,先介绍下怎么基于CMakeLists.txt构建依赖环境。

由于Google没有为googletest/samples中的samples写CMakeLists.txt,因此,gtest从github克隆下来后,也无法直接运行这些samples。

为方便大家跟着本文一起实践,获得更好的学习体验,在后台回复「gtest」即可获取我配置好的gtest压缩包。

当前目录结构如下:

$ tree -L 2
demo
├── CMakeLists.txt  
├── build        # 空的文件夹
├── include
│   ├── CMakeLists.txt
│   ├── gflags
│   └── googletest
├── main.cc
└── my_gtest_demo_1.cc

然后,在demo/build路径下,执行命令:

$ cmake .. && make -j 4

这些samples生成的可执行文件都在demo/build/bin路径下。

这样,就介绍完前提准备,下面开始进入正题。

assertion

在gtest中,是通过断言(assertion)来判断代码实现的功能是否符合预期。断言的结果分为success、non-fatal failture和fatal failture。

根据断言失败的种类,gtest提供了两种断言函数:

  • success:即断言成功,程序的行为符合预期,程序继续向下允许。

  • non-fatal failure:即断言失败,但是程序没有直接crash,而是继续向下运行。 gtest提供了宏函数EXPECT_XXX(expected, actual):如果condition(expected, actual)返回false,则EXPECT_XXX产生的就是non-fatal failure错误,并显示相关错误。

  • fatal failure:断言失败,程序直接crash,后续的测试案例不会被运行。 gtest提供了宏函数ASSERT_XXX(expected, actual)。 在写单元测试时,更加倾向于使用EXPECT_XXX,因为ASSERT_XXX是直接crash退出的,可能会导致一些内存、文件资源没有释放,因此可能会引入一些bug。

具体的EXPECT_XXX、ASSERT_XXX函数及其判断条件,如下两个表。

表1 一元比较

ASSERT

EXPECT

Verifies

ASSERT_TRUE(condition);

EXPECT_TRUE(condition);

condition is true

ASSERT_FALSE(condition)

EXPECT_FALSE(condition)

condition is false

表2 二元比较

ASSERT

EXPECT

Condition

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

Quick Start

下面以EXPECT_XXX为例子,快速开始使用gtest吧。

对于EXPECT_XXX,无论条件是否满足,都会继续向下运行,但是如果条件不满足,在报错的地方会显示:

  1. 没有通过的那个EXPECT_XXX函数位置;

  2. EXPECT_XXX第一个参数的值,即期待值

  3. EXPECT_XXX第二个参数的值,即实际值

如下demo:

// in gtest_demo_1.cc
#include <gtest/gtest.h>

int add(int lhs, int rhs) { return lhs + rhs; }

int main(int argc, char const *argv[]) {

    EXPECT_EQ(add(1,1), 2); // PASS
    EXPECT_EQ(add(1,1), 1) << "FAILED: EXPECT: 2, but given 1";; // FAILDED
    
    return 0;
}

编译执行后输出如下:

$ ./gtest_demo_1
/Users/self_study/Cpp/OpenSource/demo/gtest_demo_1.cc:9: Failure
Expected equality of these values:
  add(1,1)
    Which is: 2                # 期待的值
  1                            # 给定的值
FAILED: EXPECT: 2, but given 1 # 自己添加的提示信息 

可能你注意到了,在EXPECT_EQ(add(1,1), 1)后有个<<,这是因为gtest允许添加自定义的描述信息,当这个语句测试未通过时就会显示,比如上面的"FAILED: EXPECT: 2, but given 1"。

这个<<和std::ostream接受的类型一致,即可以接受std::ostream可以接受的类型。

相关视频推荐

程序员精进之路-从googletest测试框架开始

c++后端必学:googletest中的设计模式

c/c++后端开发需要学些什么?迭代13次的c/c++后端开发学习路线分享

需要C/C++ Linux服务器架构师学习资料加qun579733396获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

TEST

下面以googletest/samples中的sample1_unittest.cc中的demo为例,介绍如何更好地组织测试案例。

一个简单计算阶乘函数Factorial实现如下:

int Factorial(int n) {
  int result = 1;
  for (int i = 1; i <= n; i++) {
    result *= i;
  }

  return result;
}

怎么使用gtest来测试这个函数的行为?

按照上面的quick start可知,这个时候就可以使用EXPECT_EQ宏来判断:

 EXPECT_EQ(1, Factorial(-5)); // 测试计算负数的阶乘
  EXPECT_EQ(1, Factorial(0));   // 测试计算0的阶乘
  EXPECT_EQ(6, Factorial(3));   // 测试计算正数的阶乘 

但是当测试案例规模变大,不好组织。

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

用法如下:

// 下面三个 TEST 都是属于同一个 test suite,即 FactorialTest

问题来了,怎么运行这些TEST?

在sample1_unittest.cc的main函数中,添加RUN_ALL_TESTS函数即可。

int main(int argc, char **argv) {
  printf("Running main() from %s\n", __FILE__);
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS(); 
}

在build/bin路径下,执行对应的可执行文件,输出如下:

$./sample1_unittest 
Running main() from /Users/self_study/Cpp/OpenSource/demo/include/googletest/googletest/samples/sample1_unittest.cc
[==========] Running 6 tests from 2 test suites. # 在 sample1_unittest.cc 中有两个 test suites
[----------] Global test environment set-up.    

# 第一个 test suite,即上面的 FactorialTest
[----------] 3 tests from FactorialTest     # 3 组
[ RUN      ] FactorialTest.Negative         # Negative 组输出
[       OK ] FactorialTest.Negative (0 ms)  # OK 表示 Negative 组全部测试通过
[ RUN      ] FactorialTest.Zero             # Zero组输出 
[       OK ] FactorialTest.Zero (0 ms)    
[ RUN      ] FactorialTest.Positive         # Positive组输出
[       OK ] FactorialTest.Positive (0 ms)   
[----------] 3 tests from FactorialTest (0 ms total)

# sample1_unitest 另一个测试案例的输出 ...

[----------] Global test environment tear-down  
[==========] 6 tests from 2 test suites ran. (0 ms total) 
[  PASSED  ] 6 tests.              # 全部测试结果:PASS表示全部通过 

下面稍微修改下sample1_unittest.cc中的代码,来产生一个错误:

TEST(FactorialTest, Negative) {
  EXPECT_EQ(10, Factorial(-5));  // 正确的应该是  EXPECT_EQ(1, Factorial(-5));
  // ...
}

重新编译,运行结果如下:

$ ./sample1_unittest 
Running main() from /Users/self_study/Cpp/OpenSource/demo/include/googletest/googletest/samples/sample1_unittest.cc
[==========] Running 6 tests from 2 test suites.
[----------] Global test environment set-up.
[----------] 3 tests from FactorialTest
[ RUN      ] FactorialTest.Negative          # 开始运行上面修改的那个组
/Users/self_study/Cpp/OpenSource/demo/include/googletest/googletest/samples/sample1_unittest.cc:79: Failure                 # 测试失败,并指出错误case的位置
Expected equality of these values:           # 期待的值
  10
  Factorial(-5)                              # 实际计算出的值
    Which is: 1
[  FAILED  ] FactorialTest.Negative (0 ms)   # 这组case测试状态:FAILED
[ RUN      ] FactorialTest.Zero              # 下面继续运行
[       OK ] FactorialTest.Zero (0 ms)
[ RUN      ] FactorialTest.Positive
[       OK ] FactorialTest.Positive (0 ms)
[----------] 3 tests from FactorialTest (0 ms total)

# ...

[----------] Global test environment tear-down
[==========] 6 tests from 2 test suites ran. (0 ms total)
[  PASSED  ] 5 tests.          
[  FAILED  ] 1 test, listed below:     # 1个test失败
[  FAILED  ] FactorialTest.Negative    # 失败的test suite及其组

 1 FAILED TEST

此外,在TEST宏函数中,也可以像个普通函数一样,定义变量之类的行为。

比如在sample2_unittest.cc中,测试一个自定义类MyString的复制构造函数是否表现正常:

const char kHelloString[] = "Hello, world!";

// 在 TEST内部,定义变量
TEST(MyString, CopyConstructor) {
  const MyString s1(kHelloString);
  const MyString s2 = s1;
  EXPECT_EQ(0, strcmp(s2.c_string(), kHelloString));
}

为获得进一步学习,读者可以自行调整sample1_unittest.cc、sample2_unittest.cc中的TEST行为,加深对gtest的TEST宏的理解。

TEST_F

下面介绍gtest中更为高级的功能:test fixture,对应的宏函数是TEST_F(TestFixtureName, TestName)。

fixture,其语义是固定的设施,而test fixture在gtest中的作用就是为每个TEST都执行一些同样的操作。

比如,要测试一个队列Queue的各个接口功能是否正常,因此就需要向队列中添加元素。如果使用一个TEST函数测试Queue的一个接口,那么每次执行TEST时,都需要在TEST宏函数中定义一个Queue对象,并向该对象中添加元素,就很冗余、繁琐。

怎么避免这部分冗余的过程?

TEST_F就是完成这样的事情,它的第一个参数TestFixtureName是个类,需要继承testing::Test,同时根据需要实现以下两个虚函数:

  • virtual void SetUp():在TEST_F中测试案例之前运行;

  • virtual void TearDown():在TEST_F之后运行。

可以类比对象的构造函数和析构函数。这样,同一个TestFixtureName下的每个TEST_F都会先执行SetUp,最后执行TearDwom。

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

  • static void SetUpTestSuite():在第一个TEST之前运行

  • static void TearDownTestSuite():在最后一个TEST之后运行

以sample3-inl中实现的class Queue为例:

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_;
};

下面是sample3_unittest.cc中的TEST_F:

// 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_);
}

以TEST_F(QueueTestSmpl3, DefaultConstructor)为例,再具体讲解下TEST_F的运行流程:

  1. gtest构造一个QueueTestSmpl3对象t1;

  2. t1.setUp初始化t1

  3. 第一个TEST_F即DefaultConstructor开始运行并结束

  4. t1.TearDwon运行,用于清理工作

  5. t1被析构

因此,sample3_unittest.cc输出如下:

% ./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. 

TEST_F相比较TEST可以更加简洁地实现功能测试。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

手把手教你使用gtest写单元测试 的相关文章

  • 请求的资源不支持 HTTP 方法“GET”

    我的路线配置正确 并且我的方法具有装饰标签 我仍然收到 请求的资源不支持 HTTP 方法 GET 消息 System Web Mvc AcceptVerbs GET POST System Web Mvc HttpGet public st
  • 更改 Visual Studio 2015 扩展中项目内的文件 ProjectItem 的内容?

    如何更改文件的内容 ProjectItem在给定的范围内Project 我想用字符串替换它的所有内容 这个问题有解决办法吗 我想做一些改变ProjectItem CS 文件 通过使用 VSIX 包 以及我现在看到的唯一一种执行此操作的方法
  • 在 C# 中实例化 python 类

    我已经用 python 编写了一个类 我想通过 IronPython 将其包装到 net 程序集中 并在 C 应用程序中实例化 我已将该类迁移到 IronPython 创建了一个库程序集并引用了它 现在 我如何真正获得该类的实例 该类看起来
  • 根据另一个列表的内容对列表进行排序

    我有一个包含整数列表的列表和另一个包含同时包含整数和字符串的类的列表 我想做的是按字母顺序对列表进行排序 将第一个列表中存在的条目放在前面 这是我的代码和预期输出 using System using System Collections
  • ASP .NET MVC 5 - 客户地址一对一关系

    我在这里查看了论坛 实际上发现了一些类似的问题 但不是相同的问题 类似的解决方案没有给我正确的答案 我正在使用实体框架和代码优先方法来处理 ASP NET MVC 5 我想建立客户 gt 地址一对一关系的模型 我建模的是 客户等级 publ
  • 获取光标相对于控件的位置 - C#

    我想获取鼠标相对于鼠标指针所在控件的位置 这意味着当我将光标置于控件的起点 左上角 时 它应该给出 0 0 我正在使用以下代码 private void panel1 MouseMove object sender MouseEventAr
  • 使用 std::string 导致 Windows“找不到入口点”[重复]

    这个问题在这里已经有答案了 当我用 G C C 编译它时 include
  • EASTL 与 STL 相比,std::vector::operator[] 怎么会有这么大的性能差异

    根据http www open std org jtc1 sc22 wg21 docs papers 2007 n2271 html http www open std org jtc1 sc22 wg21 docs papers 2007
  • 如何在 asp .net mvc 2 中对不直接属于我的模型的对象使用 DisplayFor()?

    我确信我在这里遗漏了一些非常简单的东西 我创建了一个自定义日期时间显示模板 使用以下方法时效果很好 但是 我遇到了这样的情况 在部分控件内 我在 for 循环中迭代模型中的对象 我想要一个 DateTime 属性来使用显示模板 但我不知道如
  • 如何检查是否发生溢出? [复制]

    这个问题在这里已经有答案了 可能的重复 检测 C C 中整数溢出的最佳方法 https stackoverflow com questions 199333 best way to detect integer overflow in c
  • 当应用程序未聚焦时监听按键

    我有一个应用程序 C 4 0 WPF 它是隐藏的 可以通过单击系统托盘图标或我创建的其他框架 停靠在左侧和最上面的小框架 来显示 My customer wants to add a new way to display the appli
  • C++ 静态工厂构造函数

    我正在进行模拟 它需要创建多个相当相似的模型 我的想法是有一个名为 Model 的类并使用静态工厂方法来构造模型 例如 模型 createTriangle or 模型 createFromFile 我从以前的 java 代码中汲取了这个想法
  • 当“多次安装 MSBuild”时,Dotnet 项目转换尝试转换失败

    try convert w Test csproj target framework netstandard2 0 结果是 Multiple installs of MSBuild detected please select one In
  • g++4.9 不支持 std::align

    在学习对齐问题等时 我意识到我的 g 4 9 macports OS X 实现不支持std align 如果我尝试编译 使用 std c 11 此示例代码来自http www cplusplus com reference memory a
  • 如何在OpenGL中像这样绘制连接的带状线

    我想用以下方式绘制一系列连接线 GL LINE STRIP 我尝试过自己编写代码 但没有得到想要的结果 所以我来到这里 帮助我找出我错在哪里 这里我只给出我的draw 函数 glBegin GL LINE STRIP glVertex2f
  • 正则表达式基于组的不同替换?

    所以我对正则表达式比较陌生 并且做了一些练习 我正在玩一个简单的 混淆器 它只是寻找 dot or dot or at or at 不区分大小写 并且在匹配项之前或之后有或没有任意数量的空格 这是针对通常情况的 someemail AT d
  • Python 中的 C 指针算术

    我正在尝试将一个简单的 C 程序转换为 Python 但由于我对 C 和 Python 都一无所知 这对我来说很困难 我被 C 指针困住了 有一个函数采用 unsigned long int 指针并将其值添加到 while 循环中的某些变量
  • 为什么没有参数的函数(与实际函数定义相比)可以编译?

    我刚刚看到某人的 C 代码 我很困惑为什么要编译它 有两点我不明白 The 函数原型与实际函数定义相比没有参数 中的参数函数定义没有类型 include
  • 从哪里开始阅读 SQLite 源代码? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我想了解sqlite是如何实现的 并且 想阅读源代码 我已经下载了源代码 我应该开始查看代码的哪一部分 SQLite文档页 http
  • AddressAccessDeniedException :无需 netsh 即可解决它?

    我遇到了异常AddressAccessDeniedException因为我的processus没有注册URL的权限 我首先以管理员身份运行我的程序 好的 它成功了 但我现在想要分发我的应用程序 并且我希望每个用户都能够运行它 而不必成为管理

随机推荐

  • Linux僵尸进程处置

    导读 一般情况下 程序调用exit 包括 exit和 Exit 它们的区别这里不做解释 它的绝大多数内存和相关的资源已经被内核释放掉 但是在进程表中这个进程项 entry 还保留着 进程ID 退出状态 占用的资源等等 一 什么是僵死进程 一
  • 【算法】斐波那契(Fibonacci )数列第N项

    一 int fib2 int n if n 0 return 0 if n 1 return 1 return fib2 n 1 fib2 n 2 二 int fib int n int result 2 0 1 if n lt 2 ret
  • 【Bootstrap】常用组件(框架)

    Bootstrap常用组件 目录 1 网格系统 Grid System 网格系统的工作原理 不同设备的尺寸定义与其对应类名 基本的网格结构 偏移列 2 Bootstrap 表格 3 容器container类 4 Bootstrap 按钮 5
  • Burp Suite配置代理

    1 打开burp工具后按照下图的步骤 2 点开Add后如下弹窗 输入端口号和地址后点击ok即可
  • 正则表达式基础语法大全

    正则表达式基础语法 1 普通字符 字母 数字 汉子 下划线 以及没有特殊定义的标点符号 都是 普通字符 表达式中的普通字符 在匹配一个字符串的时候 匹配与之相同的一个字符 2 简单的转义字符 3 标准字符集合 能够与 多种字符 匹配的表达式
  • 查看oracle数据库防火墙设置,用三个方法设置Oracle数据库穿越防火墙

    用三个方法设置Oracle数据库穿越防火墙 方法一 在系统注册表中 hkey local machinesoftwareoraclehome0下加入字符串值 USE SHARED SOCKET TRUE 方法二 1 首先 我们需要将数据库实
  • x264中open_file_yuv函数欣赏(顺便谈谈如何利用指针在被调函数中改变主调函数中变量的值)

    先来看一个结构体yuv input t typedef struct FILE fh int width height int next frame yuv input t yuv input t结构体用fh这个文件指针打开原始的yuv文件
  • 超详细的VSCode下载和安装教程(非常详细)从零基础入门到精通,看完这一篇就够了。

    文章目录 1 引言 2 下载VSCode 3 解决VSCode下载速度特别慢 4 安装VSCode 1 引言 今天用WebStorm运行前端代码时 发现不太好打断点 于是 打算改用VSCode来运行前端代码 但前提是要安装VSCode 如下
  • c,c++小白到大神系列教程之一:C语言入门-王健伟-专题视频课程

    c c 小白到大神系列教程之一 C语言入门 1127人已学习 课程介绍 本课程针对 有一点计算机基础比如知道二进制 八进制 十六进制数据的含义 对内存 堆 栈等有基本概念的计算机初学者 全面介绍C语言精华内容以及利用C语言进行程序设计的方法
  • 三角脉冲信号的表达式_【信号处理工具箱】—信号表示方法

    1 工具箱中常见的函数 1 sawtooth函数 sawtooth函数用于产生锯齿波或三角波信号 格式如下 t 0 0 0001 1 y sawtooth 2 pi 50 t subplot 211 plot t y axis 0 0 2
  • 用java做一个超级马里奥的小游戏

    好的 首先你需要准备一些基本的知识和工具 了解 Java 语言的基本语法和编程概念 安装好 Java 开发环境 比如 Eclipse 或者 IntelliJ IDEA 准备好一些图像和音频资源 用于游戏中的背景 角色 音效等元素 接下来 你
  • wazuh 收集 suricata eve.json日志

    安装suricata和规则 源码或者安装包 本博客提供安装包操作方式 切换成超级用户进行操作 yum y install epel release wget jq curl O https copr fedorainfracloud org
  • 2013豆瓣校园招聘研发类笔试题

    2013豆瓣校园招聘研发类笔试题 1 将一个递归算法改为对应的非递归算法时 通常需要使用 A 优先队列 B 队列 C 循环队列 D 栈 2 爸爸 妈妈 妹妹 小强 至少两个人同一生肖的概率是多少 A 41 96 B 55 96 C 72 1
  • qqkey获取原理_通过call获取qqkey支持最新版

    如果真 进程 是否存在 TIM exe 假 且 进程 是否存在 QQ exe 假 str 你还没有登录QQ 返回 0 如果真结束 如果真 进程 是否存在 QQ exe pid 进程 取同名ID QQ exe pids 计次循环首 pid i
  • python Web开发 flask轻量级Web框架

    O flask介绍 Flask是一个使用 Python 编写的轻量级 Web 应用框架 其 WSGI 工具箱采用 Werkzeug 模板引擎则使用 Jinja2 Flask使用 BSD 授权 Flask也被称为 microframework
  • 数据结构题目-稀疏矩阵

    目录 问题 AU 函数可变参数练习 附加代码模式 问题 AV 多维下标向一维下标的换算 问题 AW 稀疏矩阵类型判断 问题 AX 稀疏矩阵转换成简记形式 附加代码模式 问题 AY 根据三元组输出稀疏矩阵 问题 AZ 三元组法表示的稀疏矩阵
  • python3.7解决ModuleNotFoundError: No module named '_bz2'

    安装完python3 7之后运行一个软件提示错误 from bz2 import BZ2Compressor BZ2Decompressor ModuleNotFoundError No module named bz2 解决方法如下 一
  • Linux内存管理子系统

    1 Linux子系统 Linux内核组成 SCI系统调用接口 PM进程管理子系统 MM内存管理子系统 Arch体系结构相关代码 DD驱动程序 Network Stack网络协议站 VFS虚拟文件系统 DD驱动程序 2 Linux内存管理子系
  • 《Apache MINA 2.0 用户指南》第十二章:日志过滤器

    后台 用户开放基于Apache MiNa的应用程序 用户可以在应用程序中创建日志管理 SLF4J MINa采用SLF4j作为日志输出 你可以在这里发现很多关于SLF4j的相关介绍 这个日志工具允许任何形式的日志系统实施 你可能使用 log4
  • 手把手教你使用gtest写单元测试

    开源框架 gtest 它主要用于写单元测试 检查真自己的程序是否符合预期行为 这不是QA 测试工程师 才学的 也是每个优秀后端开发codoer的必备技能 本期博文内容及使用的demo 参考 Googletest Basic Guide 1