Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >C/C++生态工具链——单元测试工具Catch2简介

C/C++生态工具链——单元测试工具Catch2简介

作者头像
Coder-ZZ
发布于 2023-02-23 08:45:20
发布于 2023-02-23 08:45:20
2K01
代码可运行
举报
文章被收录于专栏:C/C++进阶专栏C/C++进阶专栏
运行总次数:1
代码可运行

一,关于单元测试

单元测试指对软件中的最小可测试单元进行检查和验证,软件中的最小可测试单元有函数、接口、类等。测试时,最小可测试单元与程序中的其他部分相隔离。常用的单元测试框架有: Catch、Boost.Test、googletest、UnitTest++。

常见的两种测试模式:TDD(测试驱动开发)和BDD(行为驱动开发)。

二,TDD模式简介

测试驱动开发 (TDD,全称test-driven-development) 是一种软件开发实践,专注于在开发实际代码之前创建单元测试用例。它是一种迭代式的软件开发流程,在迭代的过程中将编码、单元测试和代码重构结合起来。TDD在测试失败时修改或编写新代码,防止重复测试同一个bug。

TDD的步骤

1.根据对功能的假设来创建测试单元

2.测试失败后更改代码,直到运行正常

3.重构代码。检查冗余的代码,优化代码的结构。

TDD的优点

大大减少了开发时导致的缺陷数量。

后续花在调试上的时间会更少。

新功能的添加和测试变得更加容易。

测试覆盖率高于传统的开发模式。

三,BDD模式简介

行为驱动开发(BDD,全称behavior-driven-development),是基于TDD做的修改,BDD和TDD之间有很多相似之处,因为它们都需要开发人员在编写代码之前先编写测试用例以通过测试。但是TDD更侧重于单独测试较小的功能,而BDD更侧重于从用户的角度验证应用程序的业务功能。

BDD的步骤

1.给定业务功能的场景

2.定义场景的执行步骤,编写测试用例

3.运行执行步骤的测试代码,如果失败了,修改步骤对应的代码,直到测试通过

BDD的语言描述形式

GIVE-WHEN-THEN模式, 参考下面两个DSL语言样例

场景1:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Scenario: Blog Search
    Given I visit the blog page
    When I search forBDD”
    Then I get posts related to BDD

场景2:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Scenario: user logs in to application
    Given authorized user “John”
    When I enter “John” in the username field
    And I enter “sekret1” in the password field
    And I click the login button
    Then the homepage should open

BDD的优点

由于BDD使用非常简单的语言来描述测试过程,更方便沟通和迭代,使产品经理、开发者和测试者都可以深入了解项目的进展,使开发出来的产品可以快速响应用户的反馈和需求。BDD可以最大限度的减少因误解需求和验收标准而导致的返工。

下面开始介绍Catch2的用法,并利用Catch2实现BDD风格的测试。

四,Catch2介绍

Catch2是主要用于C++开发场景的单元测试框架,用法和googletest有几分相似,但是定义测试用例名称的时候不需要像googletest那样严格,googletest要求必须是有效的C++变量名且不包含C++关键字。

这个”拿捏“的手势就是Catch2的官方logo

Catch2的特性

仅使用头文件就可以完成测试样例构建,无其他依赖库。

支持自注册函数。比如,我们可以使用Catch2提供的main()函数,也可以自己定义注册一个main()函数。

支持BDD测试模式,可以使用Given-When-Then模式来做BDD测试。

测试用例之间相互隔离,同一个测试用例内部,又可以分割为多个section,每个section都是独立的运行单元。

测试用例命名时支持自由格式的字符串命名。

Catch2的安装和CMake集成

1.安装Catch2的方式

(1).直接下载头文件,然后直接在项目中使用头文件。

头文件使用方式 :

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define CATCH_CONFIG_MAIN#include <catch2/catch.hpp>

当有多个cpp文件包含Catch2实现的测试用例时,只能有一个cpp文件有“#define CATCH_CONFIG_MAIN”宏定义,不然会报错。

(2).从git仓库下载完整的Catch2源代码,编译后开始使用。这个推荐新手使用,因为里面还包含了测试代码样例,方便学习。

下载编译方式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git clone https://github.com/catchorg/Catch2.git
$ cd Catch2
$ cmake -Bbuild -H. -DBUILD_TESTING=OFF
$ sudo cmake --build build/ --target install

2.Catch2在CMake中的集成

方式1,依赖库模式

先利用CMake将Catch2完整项目代码导出成依赖库(Catch2::Catch2和Catch2::Catch2WithMain两个依赖库),然后用target_link_libraries函数链接这两个依赖库。

CMake语句样例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
find_package(Catch2 3 REQUIRED)
#不需要自定义main()函数时使用
add_executable(tests_01 test.cpp)
target_link_libraries(tests_01 PRIVATE Catch2::Catch2WithMain)

#需要自定义main()函数时使用
add_executable(tests_02 test.cpp main.cpp)
target_link_libraries(tests_02 PRIVATE Catch2::Catch2)

Catch2依赖库和目标程序代码放在同一个目录下时使用find_package,Catch2依赖库放在子目录下(比如lib文件夹)时,使用add_subdirectory(lib/Catch2)。

方式2,头文件模式

利用target_include_directories函数将Catch2头文件所在的路径告诉给编译器

CMake语句样例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
set(CATCH_INCLUDE_DIR  ${CMAKE_CURRENT_SOURCE_DIR}/catch2)

add_library(Catch2 INTERFACE)
target_include_directories(Catch2 INTERFACE ${CATCH_INCLUDE_DIR})

target_link_libraries(cpp_test Catch2)

Catch2的使用方式

基本用法:

step.01 引入相关的头文件和宏定义。如果需要自己定义main()函数,还需要定义宏CATCH_CONFIG_RUNNER。

step.02 利用TEST_CASE宏定义一个测试样例。TEST_CASE需要传入两个字符串类型的参数:一个表示测试用例的名称,一个表示测试用例的标签(可选)。

step.03 编写测试逻辑。

step.04 执行测试代码。

测试代码样例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <catch2/catch_test_macros.hpp>

static int Factorial( int number ) {
   return number <= 1 ? 1 : Factorial( number - 1 ) * number;  
}

TEST_CASE( "Factorial of 0 is 1 (fail)", "[single-file]" ) {
    REQUIRE( Factorial(0) == 1 );
}

TEST_CASE( "Factorials of 1 and higher are computed (pass)", "[single-file]" ) {
    REQUIRE( Factorial(1) == 1 );
    REQUIRE( Factorial(2) == 2 );
    REQUIRE( Factorial(3) == 6 );
    REQUIRE( Factorial(10) == 3628800 );
}

Catch2常用的关键字语法

1.断言:REQUIRE和CHECK

REQUIRE:测试失败后中止测试用例

CHECK:测试失败后继续执行

样例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
CHECK( str == "string value" );
CHECK( thisReturnsTrue() );
REQUIRE( i == 42 );

2.匹配器:Matchers

匹配器可以理解成场景更复杂的断言。

样例: 推断字符串是否以“as a service”子字符串结尾,并且包含”webserver“子字符串。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
using Catch::Matchers::EndsWith;
using Catch::Matchers::ContainsSubstring;

REQUIRE_THAT( getSomeString(),
              EndsWith("as a service") && ContainsSubstring("webserver"));

3.测试用例分片:SECTION

每个section都是一段独立的执行逻辑,与其他section无关

样例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
TEMPLATE_TEST_CASE( "vectors can be sized and resized", "[vector][template]", int, std::string, (std::tuple<int,float>) ) {

    std::vector<TestType> v( 5 );

    REQUIRE( v.size() == 5 );
    REQUIRE( v.capacity() >= 5 );

    SECTION( "resizing bigger changes size and capacity" ) {
        v.resize( 10 );

        REQUIRE( v.size() == 10 );
        REQUIRE( v.capacity() >= 10 );
    }
    SECTION( "resizing smaller changes size but not capacity" ) {
        v.resize( 0 );

        REQUIRE( v.size() == 0 );
        REQUIRE( v.capacity() >= 5 );

        SECTION( "We can use the 'swap trick' to reset the capacity" ) {
            std::vector<TestType> empty;
            empty.swap( v );

            REQUIRE( v.capacity() == 0 );
        }
    }
    SECTION( "reserving smaller does not change size or capacity" ) {
        v.reserve( 0 );

        REQUIRE( v.size() == 5 );
        REQUIRE( v.capacity() >= 5 );
    }
}

4.基准测试:BENCHMARK

样例:针对斐波拉契的样本量做基准测试

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
std::uint64_t Fibonacci(std::uint64_t number) {
    return number < 2 ? 1 : Fibonacci(number - 1) + Fibonacci(number - 2);
}

TEST_CASE("Fibonacci") {
    CHECK(Fibonacci(0) == 1);
    // some more asserts..
    CHECK(Fibonacci(5) == 8);
    // some more asserts..

    // now let's benchmark:
    BENCHMARK("Fibonacci 20") {
        return Fibonacci(20);
    };

    BENCHMARK("Fibonacci 25") {
        return Fibonacci(25);
    };

}

五,基于Catch2的BDD测试

测试代码样例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SCENARIO( "vector can be sized and resized" ) {
    GIVEN( "An empty vector" ) {
        auto v = std::vector<std::string>{};

        // Validate assumption of the GIVEN clause
        THEN( "The size and capacity start at 0" ) {
            REQUIRE( v.size() == 0 );
            REQUIRE( v.capacity() == 0 );
        }

        // Validate one use case for the GIVEN object
        WHEN( "push_back() is called" ) {
            v.push_back("hullo");

            THEN( "The size changes" ) {
                REQUIRE( v.size() == 1 );
                REQUIRE( v.capacity() >= 1 );
            }
        }
    }
}

测试代码产生的BDD测试场景

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Scenario : vector can be sized and resized
  Given  : An empty vector
  Then   : The size and capacity start at 0

Scenario : vector can be sized and resized
  Given  : An empty vector
  When   : push_back() is called
  Then   : The size changes

六,完整工程演示

参考项目

https://github.com/softwareschneiderei/catch_and_jenkins

项目结构

CMake中关于Catch2的配置

用target_include_directories函数指明头文件catch.hpp的位置

测试代码

string_utils.test.cpp文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <catch.hpp>
#include "string_utils.h"

SCENARIO("strings can be left-padded") {
  GIVEN("a string") {
    using namespace std::string_literals;
    auto string = "schneide"s;

    WHEN("the string is padded with lower length") {
      auto length = 4;
      REQUIRE(length < string.length());
      THEN("it stays the same") {
        REQUIRE(string_utils::left_pad(string, length) == string);
      }
    }

    WHEN("the string is padded with a higher length") {
      std::size_t length = 16;
      REQUIRE(length > string.length());
      THEN("empty characters are inserted to the left and the length is changed") {
        auto padded = string_utils::left_pad(string, length);
        REQUIRE(length == padded.length());
        auto padding = padded.substr(0, length - string.length());
        REQUIRE(padding == std::string(8, ' '));
      }
    }
  }
}

编译执行命令

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#编译
mkdir build
cd build/
cmake ..
make

#运行测试样例
cd tests/
./tests

运行结果

参考阅读:

https://www.tutorialspoint.com/software_testing_dictionary/test_driven_development.htm

https://cucumber.io/docs/bdd/

https://www.clariontech.com/blog/bdd-agile-development-process

https://github.com/catchorg/Catch2/tree/devel/examples

https://schneide.blog/2017/12/11/integrating-catch2-with-cmake-and-jenkins/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-10-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员与背包客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
LeetCode-167.TwoSumII
Given an array of integers that is already sorted in ascending order, find two numbers such that they add up to a specific target number.
王强
2021/01/18
3680
单元测试工具(连载4)
可以看出,断言在JUnit测试中的重要性,JUnit最后是通过断言来决定测试用例通过与否。下面来看看常见的断言,如表1所示。
顾翔
2019/12/12
5670
单元测试工具(连载4)
LeetCode-15.3Sum
1.描述 Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0?
王强
2021/02/05
4030
LeetCode-15.3Sum
C++那些事之项目篇Catch2
今天推荐一个值得学习的开源项目"Catch2" ,之前写过如何使用google的googletest编写单元测试,你会发现需要编译生成lib库,比较麻烦,而Catch2是一个Header only库,能够快速使用,只需要引入header file,便可以直接使用,本节的练习代码将会在星球提供,已在星球的阅读下载即可,不在的可以扫末尾二维码加入哦。
公众号guangcity
2023/09/02
5130
C++那些事之项目篇Catch2
单元测试 & mocha 简述
单元测试 & mocha 简述 1. 单元测试 单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证 这个最小测试单元,可以是一个函数,可以是一个类,可以是一个对象,也可以
IMWeb前端团队
2017/12/29
8460
单元测试 & mocha 简述
测试工具 mocha 用法小结
腾讯IVWEB团队
2017/03/13
1.8K0
【C++篇】从零实现 C++ Vector:深度剖析 STL 的核心机制与优化
接上篇【C++篇】解密 STL 动态之魂:全面掌握 C++ vector 的高效与优雅 在现代 C++ 编程中,容器类 vector 是不可或缺的数据结构。作为一个动态数组,它提供了高效的随机访问和动态内存管理。为了加深对 vector 的理解,本文将从零开始模拟实现一个 vector,详细解析其核心机制。我们不仅会展示基础的构造、拷贝、扩展和元素插入操作,还将采用现代 C++ 的最佳实践来优化代码,尤其是在异常安全和性能上。
半截诗
2024/10/09
4750
c++单元测试
单元测试是指,对软件中的最小可测试单元在与程序其他部分相隔离的情况下进行检查和验证的工作,这里的最小可测试单元通常是指函数、接口或者类。
杨永贞
2022/08/11
1.5K0
c++单元测试
【可测试性实践】C++ 单元测试&代码覆盖率统计
最近在调研C++工程怎么做单元测试和代码覆盖率统计,由于我们工程有使用Boost库,尝试使用Boost.Test来实现单元测试并通过Gcov和Lcov来生成代码覆盖率报告。本文记录完整的搭建测试Demo,希望能带来一定参考。
巫山老妖
2024/09/12
3200
【可测试性实践】C++ 单元测试&代码覆盖率统计
LeetCode-0001.TwoSum
Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.
王强
2021/01/21
4000
STL之vector篇(下)(手撕底层代码,从零实现vector的常用指令,深度剖析并优化其核心代码)
在编程的浩瀚星空中,数据结构如同指引方向的星辰,它们不仅是构建算法与程序大厦的基石,更是解决复杂问题、优化性能的关键所在。其中,vector作为C++标准模板库(STL)中的一颗璀璨明珠,凭借其灵活、高效的特点,在软件开发中占据了举足轻重的地位。
suye
2024/10/16
4380
STL之vector篇(下)(手撕底层代码,从零实现vector的常用指令,深度剖析并优化其核心代码)
前端单元测试总结_javascript单元测试
目前,前端的测试框架很多,像QUnit、jasmine、mocha、jest、intern等框架,这些框架各有特点,简单描述下,感兴趣的可以具体研究:
全栈程序员站长
2022/09/20
1.6K0
C++代码调试和测试:使用调试器和单元测试工具
在软件开发中,调试和测试是非常重要的步骤,它们可以帮助我们发现和修复代码中的错误,确保软件的质量和可靠性。本篇文章将介绍如何使用调试器和单元测试工具来调试和测试 C++ 代码。
大盘鸡拌面
2023/12/05
7870
单元测试工具(连载10)
from email.mime.multipart import MIMEMultipart
顾翔
2019/12/12
5910
【C++篇】深入剖析C++ Vector底层源码及实现机制
接上篇:【C++篇】探索STL之美:vector容器讲解_c++vector容器-CSDN博客
熬夜学编程的小王
2024/11/21
2670
单元测试工具(连载13)
Pytest可以通过fixtures、Mark_Usefixtures和外部数据对测试用例进行参数化。
顾翔
2019/12/12
3840
单元测试工具(连载13)
前端单元测试那些事
Jest 是 Facebook 开源的一款 JS 单元测试框架,它也是 React 目前使用的单元测试框架,目前vue官方也把它当作为单元测试框架官方推荐 。 目前除了 Facebook 外,Twitter、Airbnb 也在使用 Jest。Jest 除了基本的断言和 Mock 功能外,还有快照测试、实时监控模式、覆盖度报告等实用功能。 同时 Jest 几乎不需要做任何配置便可使用。
树酱
2020/07/03
4.5K0
前端单元测试那些事
【C++】vector的基本使用
1. vector底层也是用动态顺序表实现的,和string是一样的,但是string默认存储的就是字符串,而vector的功能较为强大一些,vector不仅能存字符,理论上所有的内置类型和自定义类型都能存,vector的内容可以是一个自定义类型的对象,也可以是一个内置类型的变量。
举杯邀明月
2023/04/12
9970
【C++】vector的基本使用
C++初阶:容器(Containers)vector常用接口详解
介绍完了string类的相关内容后:C++初阶:适合新手的手撕string类(模拟实现string类) 接下来进入新的篇章,容器vector介绍:
是Nero哦
2024/02/09
2180
C++初阶:容器(Containers)vector常用接口详解
单元测试工具(连载11)
表5 pytest的装饰器
顾翔
2019/12/12
5840
单元测试工具(连载11)
相关推荐
LeetCode-167.TwoSumII
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验