前言
如果你见过我,你可能会知道我是自动化测试的忠实信徒。即使对于小型项目,我也倾向于在早期实施一些测试,对于大型项目,我认为测试是绝对必要的。我可以花很长时间来讲为什么测试很重要,而你应该这样做,但这不是今天的主题。相反,我将介绍为什么我将所有单元测试从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?对我来说,主要有这几个原因:
- 简单的设置 - 它总是只需要一个头文件,不需要手动组合。
- 没有Fix图热水!
- 更具表现力的匹配者。
讯享网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。现在我需要一些东西,但是:
- 移植上几千个测试,包括从Google Test到Catch的数万个测试宏。
- 为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非常易于集成,测试简洁易读,编译时间和运行时性能都不会成为我的问题。

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