C++开发(二)

C++开发(二)

1. GoogleTest测试框架

基本测试用例

基本测试用例是GoogleTest中最简单的测试单元,用于测定某个特定功能的行为。每个测试用例都是一个独立的函数,以TEST宏定义。

  • TEST宏:TEST宏定义了一个测试用例。它接收两个参数,第一个是测试用例集的名称(通常用类名或模块名),第二个是测试用例的名称。
  • GoogleTest提供了多种断言来验证测试结果,如EXPECT_EQ(相等)、EXPECT_TRUE(为真)等。
1
2
3
4
5
6
7
8
9
#include "gtest/gtest.h"
#include "my_module.h" // 假设这是你要测试的模块

TEST(MyModuleTest, TestFunctionality) {
MyModule module;
int result = module.SomeFunction();
EXPECT_EQ(result, 42); // 验证返回值是否为42
}

常用断言:

EXPECT_EQ(val1, val2): 验证 val1val2 是否相等。

EXPECT_NE(val1, val2): 验证 val1val2 是否不相等。

EXPECT_TRUE(condition): 验证条件 conditiontrue

EXPECT_FALSE(condition): 验证条件 conditionfalse

EXPECT_NEAR(val1, val2, abs_error): 验证 val1val2 之间的差值是否在 abs_error 之内(适用于浮点数)。

测试夹具类

测试夹具类用于在多个测试用例之间共享共同的设置和清理代码。通过使用测试夹具类,可以避免重复代码,提升测试的可维护性。

  • 当多个测试用例需要相同的初始化和清理工作时。
  • 当需要在测试用例之间共享资源(如文件、数据库连接等)时。

测试夹具类需要继承::testing::Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "gtest/gtest.h"
#include "my_module.h"

class MyModuleTest : public ::testing::Test {
protected:
void SetUp() override {
module = new MyModule();
}

void TearDown() override {
delete module;
}

MyModule* module;
};

TEST_F(MyModuleTest, TestFunctionality) {
EXPECT_EQ(module->SomeFunction(), 42);
}

TEST_F(MyModuleTest, AnotherTest) {
EXPECT_TRUE(module->AnotherFunction());
}

  • override关键字是重写关键字,在CXX中,没有这个关键字子类也可以重写父类的方法。但是有这个关键字以后,编译器会进行检查。

  • 虚函数是为了多态用的,可以用基类指针调用派生类的方法;纯虚函数是为了定义接口类用的。

  • SetUp()方法:这个方法在每个测试用例之前自动调用,用于设置测试环境,比如初始化对象或分配资源。

  • TearDown()方法:这个方法在每个测试用例之后自动调用,用于清理测试环境,比如释放资源或关闭连接。

  • 一旦定义了测试夹具类,就可以在测试用例中使用TEST_F宏来编写测试用例。

假设你有一个类 Calculator,其中有多个方法需要测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Calculator {
public:
Calculator(int a, int b) : a_(a), b_(b) {}
int Add() const { return a_ + b_; }
int Subtract() const { return a_ - b_; }
private:
int a_, b_;
};

class CalculatorTest : public ::testing::Test {
protected:
void SetUp() override {
calculator = new Calculator(10, 5);
}

void TearDown() override {
delete calculator;
}

Calculator* calculator;
};

TEST_F(CalculatorTest, AddTest) {
EXPECT_EQ(calculator->Add(), 15);
}

TEST_F(CalculatorTest, SubtractTest) {
EXPECT_EQ(calculator->Subtract(), 5);
}

在这个例子中,CalculatorTest 是一个测试夹具类,它在 SetUp() 中创建 Calculator 对象,并在 TearDown() 中销毁它。每个测试用例都会初始化一个独立的Calculator对象实例,这个夹具节省了代码。

2. CMake基本语法

CMake使用配置文件CMakeLists.txt,该文件包含了项目的构建规则和依赖项。以下是CMake的一些基本语法和常用指令。:

  1. 最小要求版本

    可以在CMakeLists.txt文件的开头指定CMake的最低版本要求:

    1
    cmake_minimum_required(VERSION 3.10)
  2. 项目名称

    1
    project(MyProject LANGUAGES CXX)
  3. 添加可执行文件

    1
    add_executable(MyExecutable main.cpp)
  4. 添加库

    1
    add_library(MyLibrary STATIC mylib.cpp)
    • MyLibrary是库的名称。
    • STATIC表示创建静态库。
    • mylib.cpp是源文件。
  5. 指定编译选项

    target_compile_options为目标添加编译选项:

    1
    target_compile_options(MyExecutable PRIVATE -Wall -Wextra)
  6. 使用target_include_directories指定头文件目录:

    1
    target_include_directories(MyExecutable PRIVATE ${CMAKE_SOURCE_DIR}/include)
  7. 链接库

    使用 target_link_libraries 将库链接到目标:

    1
    target_link_libraries(MyExecutable PRIVATE MyLibrary)
  8. 添加子目录

    可以将子目录添加到构建中:

    1
    add_subdirectory(src)
  9. 条件语句

    使用条件语句执行特定操作:

    1
    2
    3
    if (CMAKE_BUILD_TYPE STREQUAL "Debug")
    message("Debug build")
    endif()
  10. 自定义变量

    1
    set(MY_VAE "Hello World")
  11. 查找包

    1
    find_package(Boost 1.65 REQUIRED)
  12. 生成输出

    1
    message(STATUS "Building MyProject")
  13. 安装指令

    1
    install(TARGETS MyExecutable DESTINATION bin)
  • 关于`include_directories()``

    include_directories() 在 CMake 中的作用范围取决于它的定义位置。它会影响在其之后定义的所有目标(如 add_executable()add_library()),但这并不意味着它总是面向所有的可执行文件。

    如果你在 CMakeLists.txt 文件的全局范围(即没有嵌套在任何函数或子目录中)使用 include_directories(),那么它将对在该指令之后定义的所有目标生效。这包括所有在同一个 CMakeLists.txt 文件中之后定义的 add_executable()add_library()

    例如:

1
2
3
4
include_directories(${GTEST_INCLUDE_DIR})

add_executable(MyExecutable1 main.cpp)
add_executable(MyExecutable2 another_main.cpp)

在这个例子中,MyExecutable1MyExecutable2 都会包含 ${GTEST_INCLUDE_DIR} 目录。

  • CMake 3.0 及以上版本更推荐使用 target_include_directories(),它允许你为特定的目标(可执行文件或库)设置包含目录,而不是对所有目标生效。这种方式更加灵活,尤其是在你有多个目标但它们需要不同的包含目录时。

    例如:

1
2
3
4
add_executable(MyExecutable1 main.cpp)
add_executable(MyExecutable2 another_main.cpp)

target_include_directories(MyExecutable1 PRIVATE ${GTEST_INCLUDE_DIR})

在 CMake 中,PRIVATEtarget_include_directories()target_link_libraries() 等指令中的一种访问控制修饰符,用来控制目标之间的依赖关系以及这些依赖项如何传播。CMake 提供了三种主要的修饰符:PRIVATEPUBLICINTERFACE。每种修饰符都有不同的传播规则:

  1. PRIVATE

    • 仅对当前目标可见PRIVATE 修饰符表示该依赖项或包含目录只适用于当前目标,不会传播到依赖当前目标的其他目标。换句话说,当前目标在编译时会使用这些包含目录或链接库,但是依赖它的目标不会继承这些信息。
    • 典型场景:当你有一个库或可执行文件,它使用了一些依赖项(如特定的库或包含目录),但这些依赖项不需要对其他依赖它的目标公开时,使用 PRIVATE 是合适的选择。
    1
    target_include_directories(MyExecutable PRIVATE ${GTEST_INCLUDE_DIR})

    在这个例子中,${GTEST_INCLUDE_DIR} 只对 MyExecutable 这个目标可见,不会传播给依赖 MyExecutable 的其他目标。

  2. PUBLIC

    • 对当前目标和依赖它的目标可见PUBLIC 表示该依赖项或包含目录不仅适用于当前目标,也会传播给所有依赖当前目标的其他目标。这意味着如果其他目标链接了这个目标,它们也会自动继承这些包含目录或链接库。
    • 典型场景:当你创建一个库,并且这个库的依赖项应该被所有使用该库的其他目标所知道时,使用 PUBLIC 是适合的。
    1
    target_include_directories(MyLibrary PUBLIC ${SOME_INCLUDE_DIR})

    在这个例子中,${SOME_INCLUDE_DIR} 会被 MyLibrary 及所有依赖 MyLibrary 的目标继承。

  3. INTERFACE

    • 仅对依赖目标可见INTERFACE 表示该依赖项或包含目录不会用于当前目标的编译,而是只传播给依赖当前目标的其他目标。通常用于接口库(interface library)或者当你想要公开某些依赖项,而当前目标本身不需要它们时。
    • 典型场景:如果你有一个库,自己不使用某个依赖项,但希望所有使用它的目标都能访问这个依赖项,那么可以使用 INTERFACE
    1
    target_include_directories(MyLibrary INTERFACE ${SOME_INCLUDE_DIR})

    在这个例子中,${SOME_INCLUDE_DIR} 只对依赖 MyLibrary 的目标可见,MyLibrary 本身不会使用这个目录。

这里的目标依赖都是依赖某个库,不会依赖可执行文件,所以对于可执行文件这个PRIVATE与否加不加都没什么意义。

3. GoogleTest与Cmake

在 CMake 中调用enable_testing(),它并不直接定义任何测试,而是为项目配置测试基础设施,使得可以在之后使用 add_test() 命令来定义具体的测试。

  • 当调用 enable_testing() 时,CMake 会自动生成一些内部目标和命令,允许使用 ctest 命令来运行测试。ctest 是 CMake 附带的一个测试驱动工具,它可以用于执行项目中的所有测试。

  • 通过启用测试,可以在生成的构建系统中获得一个 test 目标。例如,在 Unix 系统上,可以通过 make testninja test 来运行所有已定义的测试。

  • enable_testing() 通常与 add_test() 一起使用。add_test() 用于定义具体的测试命令,这些命令将在 ctest 运行时执行。没有 enable_testing()add_test() 将不会生效。

4. GoogleTest与Cmake与VScode

VSCode 提供了对 CMake 和 GoogleTest 的良好集成,可以通过点击直接运行某个测试。

  1. 单击测试执行:你可以直接在编辑器中点击运行某个测试,而不需要手动输入命令。这大大提高了测试的效率和便捷性。

  2. 测试结果显示:测试结果会直接显示在 VSCode 的终端或输出面板中,便于快速查看和调试。

  3. 持续集成支持:VSCode 中的 CMake 和测试集成还可以与持续集成系统很好地协作,在本地就能体验到与 CI/CD 流程类似的测试流程。

  4. 安装 CMake 和 GoogleTest 扩展:确保你已经在 VSCode 中安装了 CMakeCMake Tools 扩展。GoogleTest 的测试运行可能需要 Test Explorer 扩展。

  5. 配置 CMake:在 CMakeLists.txt 文件中正确配置测试目标和测试命令,类似于你已经做的那样。

  6. 运行和调试测试:在 VSCode 中,通过测试面板或输出窗口,可以轻松查看测试状态、运行测试,甚至调试失败的测试用例。

  • 当你在 CMake 中添加了新的测试或修改了测试代码后,你可以直接在 VSCode 中点击测试来执行,查看测试是否通过。这种工作流非常适合快速迭代和持续测试,尤其是在你用 C++ 重写代码的过程中。

如果你有其他问题,或想进一步优化这种工作流,请随时告诉我!

5. 项目经理

JiaWen Xie是项目经理,项目经理是做什么的呢?虽然这时候我是甲方,但是我也有了一个参观实践的样品,看看他们是怎么推进这个项目的。要利用好这一段集中的时间,上面用Python学习软件工程相关的内容,下面用CXX学习底层的一些知识,中间用跟他们的合作,串起来工业软件开发流程,把三种技能集成到一起互相促进。我感觉我现在的学习工作速度比以前高了很多。

PM,Project Manager,项目经理,他们对项目流程负责。正确地协调团队内部外部,调配各部门资源和时间,有效进行风险管理,保证一个项目顺利按计划结项。

在一个具体项目中,PM的任务包括

  1. 带领团队形成团队的目标/远景,把抽象的目标转化为可执行的、具体的设计。
  2. 管理软件的具体功能的生命周期。
  3. 创建并维护软件的规格说明书。
  4. 代表客户和用户的利益,收集客户反馈,与其客户需求,协调并决定各种需求的优先级。
  5. 分析并带领其他成员对缺陷/变更需求形成一致意见,并确保实施。
  6. 带领其他成员确保项目保持功能时间和资源的合理平衡,跟踪项目进展,确保团队发布令客户满意的软件。
  7. 收集团队项目管理和软件工程的各种数据,客观分析项目实施过程中的优缺点,推动项目成员持续改进。
6. 持续集成

持续集成(Continuous Integration, CI) 是一种软件开发实践,指的是开发人员频繁地将代码集成到主干代码库中,并且每次集成都通过自动化构建和测试来验证。这一实践的主要目标是尽早发现集成问题,从而提高软件质量和开发效率。

持续集成的核心理念包括:

  1. 频繁集成:开发人员频繁地将代码提交到版本控制系统(如 Git),每次提交都触发自动化构建和测试。
  2. 自动化构建和测试:一旦有代码提交,CI 系统会自动拉取最新代码,进行构建(编译、链接等),并运行一系列自动化测试(单元测试、集成测试等)。如果构建或测试失败,系统会立即通知开发人员。
  3. 快速反馈:CI 系统能够快速提供代码是否成功集成的信息,帮助开发人员及时修复问题。

持续集成的好处:

  • 提高代码质量:通过频繁的自动化测试,可以更早地发现问题,减少代码在集成时发生冲突或错误的可能性。
  • 减少风险:在较小的增量中集成代码可以减少集成时的复杂性和风险。
  • 提高开发效率:开发人员可以快速得到反馈,减少了手动测试和部署的时间。

什么是 CI/CD 系统?

CI/CD(Continuous Integration/Continuous Deployment or Continuous Delivery)系统是实现持续集成和持续部署/交付的工具平台。常见的 CI/CD 平台包括 Jenkins、GitLab CI、Travis CI、CircleCI、GitHub Actions 等。

  • 持续部署(Continuous Deployment):自动将通过测试的代码部署到生产环境。
  • 持续交付(Continuous Delivery):将通过测试的代码部署到准备发布的环境,由团队决定何时将其发布到生产环境。

CMake 与持续集成的关系

CMake 在持续集成中扮演了重要的角色,特别是在构建和测试阶段。CMake 是一个跨平台的构建系统生成工具,可以生成适用于不同平台的构建文件(如 Makefile、Visual Studio 项目等)。在 CI 流程中,CMake 常被用来配置、生成和管理构建流程。

在 CI 系统中使用 CMake 的典型流程:

  1. 代码提交:开发人员将代码提交到版本控制系统(如 Git)。

  2. CI 系统触发:CI 系统检测到新的提交后,自动触发构建和测试流程。

  3. 配置构建环境:CI 系统在一个干净的构建环境中运行 CMake,以配置项目的构建系统。通常使用类似的命令:

    1
    cmake -S . -B build

    这里,-S . 表示源代码目录,-B build 表示生成的构建文件的目录。

  4. 构建项目:一旦 CMake 配置完成,CI 系统会执行构建命令,比如:

    1
    cmake --build build
  5. 运行测试:构建成功后,CI 系统运行 CMake 配置的测试目标,比如:

    1
    ctest --output-on-failure

    这一步将运行由 enable_testing()add_test() 定义的测试用例。

  6. 报告结果:CI 系统汇总构建和测试的结果,并生成报告。如果有失败的测试或构建错误,CI 系统会通知开发团队。

    常见的 CI/CD 工具与 CMake 的集成

    • Jenkins:通过插件或直接在 Jenkinsfile 中定义 CMake 构建步骤。
    • GitLab CI:在 .gitlab-ci.yml 文件中定义 CMake 构建和测试的步骤。
    • GitHub Actions:在 .github/workflows/ 中定义工作流程,使用 CMake 执行构建和测试。
    • Travis CI:通过 .travis.yml 文件来配置 CMake 构建和测试流程。

实际案例:GitHub Actions 与 CMake

假设你有一个使用 CMake 构建的 C++ 项目,你可以通过 GitHub Actions 来实现持续集成。

.github/workflows/ci.yml 的简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
name: CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Set up CMake
uses: jwlawson/actions-setup-cmake@v1

- name: Configure CMake
run: cmake -S . -B build

- name: Build
run: cmake --build build

- name: Run tests
run: ctest --output-on-failure --test-dir build

这个工作流在每次代码推送到 main 分支时都会触发,自动运行 CMake 配置、构建和测试。

  • 持续集成(CI) 是一种实践,强调频繁集成代码并通过自动化构建和测试来验证集成的正确性。
  • CI/CD 系统 是实现持续集成和持续部署的工具平台,常用工具包括 Jenkins、GitLab CI、GitHub Actions 等。
  • CMake 是构建工具,可以很好地集成到 CI 系统中,用于配置、生成和管理跨平台的构建过程,以及运行自动化测试。

CMake 在持续集成环境中的作用主要是确保代码能够在各种环境中顺利构建并通过测试,帮助开发团队保持高质量的代码库。

接下来的半年应该是我各方面成长最快的一段时间,但可能也要同时兼顾各方面的东西,会有些精神上的劳累。