2025年基本概述 - Catch(一)

基本概述 - Catch(一)前言 如果你见过我 你可能会知道我是自动化测试的忠实信徒 即使对于小型项目 我也倾向于在早期实施一些测试 对于大型项目 我认为测试是绝对必要的 我可以花很长时间来讲为什么测试很重要 而你应该这样做 但这不是今天的主题 相反 我将介绍为什么我将所有单元测试从 Google Test

大家好,我是讯享网,很高兴认识大家。

前言

如果你见过我,你可能会知道我是自动化测试的忠实信徒。即使对于小型项目,我也倾向于在早期实施一些测试,对于大型项目,我认为测试是绝对必要的。我可以花很长时间来讲为什么测试很重要,而你应该这样做,但这不是今天的主题。相反,我将介绍为什么我将所有单元测试从Google Test(我之前使用的测试框架)移至Catch,并阐明了我如何做到这一点。在我们开始讨论之前,让我们回顾一下我是如何进入Google Test以及为什么我想首先改变一些东西。


一个简短的历史

许多月前这篇博文让我对单元测试很感兴趣。鉴于我没有任何经验,并且由于UnitTest ++看起来和任何其他框架一样好,我使用它编写了我的初始测试。这是在2008年左右的某个时候。在2010年,我对UnitTest ++感到有点沮丧,因为开发并不是那么强大,我希望有更多的测试宏用于字符串比较等等。长话短说,我最终将所有测试移植到Google Test。

在当世,Google Test是在 Google Code,上开发的,确实经常版本发布,但不是经常。将Google Test捆绑到单个文件中需要运行一个单独的工具(而且它仍然这样。)。我最终使用Google Test进行了所有测试 - 其中大约有3000个测试,其中包含大量Fixtures。在开发时,我在每个构建上运行单元测试,所以我还写了一个自定义报告者,所以我的控制台输出如下所示:

SUCCESS (11 tests, 0 ms) SUCCESS (1 tests, 0 ms) SUCCESS (23 tests 1 ms) 

讯享网
  • 1
  • 2
  • 3

您可能想知道为什么还会记录时间:鉴于每次编译都运行测试,它们运行得更快,所以我总是关注测试时间,如果事情开始变慢,我可以移动它进入一个单独的测试套件。

多年来,这对我很有帮助,但 我对Google Test仍有一些抱怨。首先,很明显这个项目是由谷歌开发的,所以他们的方向 - 死亡测试等 - 并没有让我的生活更简单。与此同时,我的视野里出现了一个新的框架:Catch。

Get Started

下载Catch头文件

  • github.com/catchorg/Catch2 下载catch2.hpp文件
  • 将catch2.hpp文件放入到本地项目

创建测试项目,目录结构如下

讯享网. ├── build ├── CMakeLists.txt ├── main.cpp └── tests ├── catch.hpp └── test.cpp 

写测试demo的测试test.cpp

#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file #include "catch.hpp" unsigned int Factorial( unsigned int number ) { return number <= 1 ? number : Factorial(number-1)*number; } TEST_CASE( "Factorials are computed", "[factorial]" ) { REQUIRE( Factorial(1) == 1 ); REQUIRE( Factorial(2) == 2 ); REQUIRE( Factorial(3) == 6 ); REQUIRE( Factorial(10) ==  ); } 

编译

1.修改CMakeLists.txt:

讯享网cmake_minimum_required(VERSION 3.14) project(catchDemo) set(CMAKE_CXX_STANDARD 14) add_executable(catchDemo tests/test.cpp) 

2.编译

cd build cmake ../ make 

3.获得可执行文件

运行可执行文件

  • ./catchDemo
  • 得到运行结果

接触Catch

你可能会问为什么要使用Catch?对我来说,主要有这几个原因:

  1. 简单的设置 - 它总是只需要一个头文件,不需要手动组合。
  2. 没有Fix图热水!
  3. 更具表现力的匹配者。


讯享网

讯享网TEST_CASE("DateTime", "[core]") { const DateTime dt (1969, 7, 20, 20, 17, 40, 42, DateTimeReference::Utc); SECTION("GetYear") { CHECK (dt.GetYear () == 1969); } SECTION("GetMonth") { CHECK (dt.GetMonth () == 7); } // 下面或许是更多的内容 } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

以上,到配上更好的匹配器(没有更多的ASSERT_EQ宏),你可以使用正常的比较。这足以让我相信Catch。现在我需要一些东西,但是:

  1. 移植上几千个测试,包括从Google Test到Catch的数万个测试宏。
  2. 为Catch实现自定义报告器。

主要特点

快速且非常容易上手。 只需下载catch.hpp,#include它就可以了。
没有外部依赖。 只要您可以编译C ++ 11并且可以使用C ++标准库。
将测试用例写为自注册函数(或方法,如果您愿意)。
将测试用例分成几个部分,每个部分都是隔离运行的(不需要固定装置)。
使用BDD样式的Given-When-Then部分以及传统的单元测试用例。
只有一个核心断言宏用于比较。 标准C / C ++运算符用于比较 - 但是完整表达式被分解并记录lhs和rhs值。
测试使用自由格式字符串命名 - 不再使用合法标识符中的连接名称。

其他核心功能

可以标记测试以便轻松运行特定的测试组。
失败可以(可选)进入Windows和Mac上的调试器。
输出是通过模块化报告对象。 包括基本的文本和XML记者。 可以轻松添加自定义记者。
支持JUnit xml输出与第三方工具(如CI服务器)集成。
提供了默认的main()函数,但您可以提供自己的完整控件(例如集成到您自己的测试运行器GUI中)。
提供了命令行解析器,如果您选择提供自己的main()函数,仍然可以使用它。
Catch可以测试自己。
备用断言宏报告失败但不中止测试用例
浮点容差比较使用富有表现力的约()语法构建。
内部和友好的宏是隔离的,因此可以管理名称冲突
匹配器


移植

由于我是一个相当懒惰的人,并且因为测试格式非常统一,所以我决定半自动化从Google Test到Catch的转换。很有可能可以制作一个完美的自动化工具,至少对于断言,通过在Clang上构建它并进行重构。但我想如果我自动完成80%左右应该仍然没问题。

在你问为什么我没有移植到像Catch这样应该更快的其他框架之前:在我的测试中,Catch足够快,以至于测试开销无关紧要。我可以在不到10毫秒的时间内轻松执行20000个断言,因此“更快”在这一点上并不是真正的论据。

有趣的是,通过转移到Catch,代码行显着减少,其中大部分是因为Fixtures已经消失,而更多的代码现在使用了SECTION宏,我可以合并公共代码。以前,我经常会复制一些小的设置,因为它比写一个Fixture打字更少。使用 Catch这很简单,我最终自愿清理我的测试。为了给你一些想法,这是核心库的提交:114个文件已更改,6717个插入(+), 6885个删除( - )(或-3%)。对于我的几何库,它有更多的设置代码,相对减少相当高:36个文件更改,2342个插入(+), 2478删除( - ) - 5%。这里和那里有几个百分点可能看起来不太重要,但由于较少的样板,它们直接转化为提高了可读性。

在一些极端情况下,Catch的行为与Google Test不同。值得注意的是,带有0 的EXPECT_FLOAT_EQ需要转换为CHECK(a == Approx (0).margin(some_eps)),因为Catch默认使用相对epsilon,当与0比较时变为0。另一个影响STREQ -在Catch中,你需要使用匹配器,它将整个测试转换为CHECK_THAT(str,Catch :: Equals(“Expected str”)); 。

Terse 报告者

最后遗漏的是简洁的报告者。Catch2再次发生了变化,这是目前的稳定版本。报告者是catch-main.cpp的一部分,我将其编译成一个静态库,然后将其链接到测试可执行文件中。简洁的报告者很简单:

namespace Catch { class TerseReporter : public StreamingReporterBase<TerseReporter> { public: TerseReporter (ReporterConfig const& _config) : StreamingReporterBase (_config) { } static std::string getDescription () { return "Terse output"; } virtual void assertionStarting (AssertionInfo const&) {} virtual bool assertionEnded (AssertionStats const& stats) { if (!stats.assertionResult.succeeded ()) { const auto location = stats.assertionResult.getSourceInfo (); std::cout << location.file << "(" << location.line << ") error\n" << "\t"; switch (stats.assertionResult.getResultType ()) { case ResultWas::DidntThrowException: std::cout << "Expected exception was not thrown"; break; case ResultWas::ExpressionFailed: std::cout << "Expression is not true: " << stats.assertionResult.getExpandedExpression (); break; case ResultWas::Exception: std::cout << "Unexpected exception"; break; default: std::cout << "Test failed"; break; } std::cout << std::endl; } return true; } void sectionStarting (const SectionInfo& info) override { ++sectionNesting_; StreamingReporterBase::sectionStarting (info); } void sectionEnded (const SectionStats& stats) override { if (--sectionNesting_ == 0) { totalDuration_ += stats.durationInSeconds; } StreamingReporterBase::sectionEnded (stats); } void testRunEnded (const TestRunStats& stats) override { if (stats.totals.assertions.allPassed ()) { std::cout << "SUCCESS (" << stats.totals.testCases.total () << " tests, " << stats.totals.assertions.total () << " assertions, " << static_cast<int> (totalDuration_ * 1000) << " ms)"; } else { std::cout << "FAILURE (" << stats.totals.assertions.failed << " out of " << stats.totals.assertions.total () << " failed, " << static_cast<int> (totalDuration_ * 1000) << " ms)"; } std::cout << std::endl; StreamingReporterBase::testRunEnded (stats); } private: int sectionNesting_ = 0; double totalDuration_ = 0; }; CATCH_REGISTER_REPORTER ("terse", TerseReporter) } 
  • 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
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

使用参数-r terse开运行测试以选择这个报告者。这会产生想洗面这样的测试报告

讯享网SUCCESS (11 tests, 18 assertions, 0 ms) SUCCESS (1 tests, 2 assertions, 0 ms) SUCCESS (23 tests, 283 assertions, 1 ms) 
  • 1
  • 2
  • 3

作为额外的奖励,它还显示了执行的测试宏的数量。这有助于识别通过一些长循环运行的测试。

结论

移植值得吗?花了一些时间进行新的Catch测试,并在编写了更多的测试后,我仍然相信它是值得的。Catch非常易于集成,测试简洁易读,编译时间和运行时性能都不会成为我的问题。

小讯
上一篇 2025-03-19 10:24
下一篇 2025-03-22 12:55

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/24363.html