美文网首页
Catch使用指南

Catch使用指南

作者: 龙翱天际 | 来源:发表于2017-09-23 18:09 被阅读76次

获取Catch

获取Catch的最简单的方法是下载最新的单头文件版本
虽然该文件是通过合并一组单独的头文件来生成的,但它仍然只是一个包含普通源代码的头文件。
Catch的完整源代码,包括测试项目,文档和其他内容,由GitHub托管。
http://catch-lib.net将为您重定向。

放哪儿?

Catch只需要一个头文件。你需要做的就是将文件放在你的项目能够包含到的地方 - 不管是放在某个您指定的第三方库的位置,并通过头文件搜索路径(如:VS下的C++目录->包含目录)来查找,还是在项目中通过目录树直接定位!
对于想要使用Catch的测试套件的其他开源项目来说,这是一个特别好的选择。
有关更多信息,请参阅博客条目
本教程的其余部分都将直接使用单头文件的Catch,当然您也可以将其放在某个文件夹来使用。

编写测试用例

我们用一个简单的例子来说明Catch的用法。假设你需要写了一个方法去计算阶乘,现在你要测试它(请先不要管测试驱动开发的问题)

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

为了简单,我们把所有的代码都写到同一个文件中(关于如何组织测试文件,详见:进一步)

#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) == 3628800 );
}

Let's add that to the test case:
这将编译成一个完整的可执行文件,它可以响应命令行参数
如果你不带任何参数运行,它将执行所有的测试用例,报告任何失败,报告有多少测试通过和失败的摘要,并返回失败的测试的数量(如何你想要的只是一个是/否的答案,也就是“代码是否能正常工作”)。

如果你运行上面的测试用例,它将通过。一切都很好。对吗?但是这里还有一个bug。问题在于什么是零的阶乘? 关于零的阶乘请点击此处
我们来补充一下测试用例:

TEST_CASE( "Factorials are computed", "[factorial]" ) {
    REQUIRE( Factorial(0) == 1 );
    REQUIRE( Factorial(1) == 1 );
    REQUIRE( Factorial(2) == 2 );
    REQUIRE( Factorial(3) == 6 );
    REQUIRE( Factorial(10) == 3628800 );
}

现在我们会得到这样的一个失败提示:

Example.cpp:9: FAILED:
  REQUIRE( Factorial(0) == 1 )
with expansion:
  0 == 1

可以看到打印出的Factorial(0) 的返回值为0,而且使用了自然的==表达式。这有助于我们直观地看到问题在哪。
我们修改下factorial函数的代码:

unsigned int Factorial( unsigned int number ) {
  return number > 1 ? Factorial(number-1)*number : 1;
}

现在所有的测试通过了。
当然了,我们可以做的事还有很多。例如:当返回值超出unsigned int的取值范围时,我们将遇到问题。函数factorials是很容易发生这种事的。你可能要为这种情况增加测试,并决定如何处理该情况。在这里,我们先不处理这样的问题。

在这里我们做了什么?

虽然这是一个简单的测试,但也已经足够了解如何Catch的一些事情。在我们继续之前,让我们花一些时间来回顾一下。

  1. 我们要做的就是#define一个标识符合#include一个头文件,然后我们就拥有了一切,甚至连main的实现都通过响应命令行参数来完成。你只会在一个cpp文件中#define,由于明显的原因(编译速度)。如果你有超过一个文件需要进行单元测试,需要你在每个待测文件中#include "catch.hpp"。通常在一个专门的cpp文件中,去#define CATCH_CONFIG_MAIN
    #include "catch.hpp"。当然,你也可以提供自己的main函数实现并自己去运行起的Catch的功能。 (详见:提供你自己的main函数)。
  2. 这里,我们通过TEST_CASE宏来介绍测试用例。该宏有一个或二个参数:一个任意字符串,以及一个或多个标签(详尽内容,请参见Test cases and Sections)。测试用例的名称必须是唯一的。你可以通过指定通配符标识测试用例名称或标签表达式来运行指定的测试用例。关于运行测试用例的更多信息,见:命令行文档
  3. 测试用例名称和标签仅仅是普通的字符串。到目前为止,我们还没声明一个方法或函数,或者显式地在任何地方注册测试用例。在幕后,通过测试用例名称为您定义了一个函数,并使用静态注册表类自动注册。通过抽取函数名称,我们可以命名我们的测试,而不受标识符名称的约束。
  4. 我们通过REQUIRE宏写单独的测试断言。我们使用自然地C/C++语义来表达条件,而不是为每一种条件定义一个单独的宏。在幕后,一组简单的表达模板捕获表达式的左侧和右侧,以便我们可以在测试报告中显示值。虽然我们稍后将看到有其他断言宏-但由于该技术,它们的数量急剧减少。

测试用例和部分

大多数测试框架都有一个基于类的套件机制。
也就是说,测试用例映射到一个类,初始化和销毁将在setup()teardown()方法中执行(在C++等语言中叫构造/析构)。虽然Catch完全支持这种工作方式,但是有一些问题。特别是你的代码必须被拆分,并且它的细粒度可能会导致问题。您在一组方法中只能使用一对setup/teardown,但是有时您希望在每个函数中设置稍稍不同的setup,或者甚至可能需要多个级别的设置(我们稍后将在本教程中对此进行阐述)。
正是这样的问题,导致James Newkirk带领团队去内置NUnit,要从头开始并构建xUnit
Catch采用不同的方法(对于NUnitxUnit),这对于C ++和C系列语言来说更为自然。
通过一个例子来完美诠释:

TEST_CASE( "vectors can be sized and resized", "[vector]" ) {

    std::vector<int> 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( "reserving bigger changes capacity but not size" ) {
        v.reserve( 10 );
        
        REQUIRE( v.size() == 5 );
        REQUIRE( v.capacity() >= 10 );
    }
    SECTION( "reserving smaller does not change size or capacity" ) {
        v.reserve( 0 );
        
        REQUIRE( v.size() == 5 );
        REQUIRE( v.capacity() >= 5 );
    }
}

we might want to verify that attempting to reserve a capacity smaller than the current capacity of the vector changes nothing. We can do that, naturally, like so:
对于每SECTION和每一个TEST_CASE都是从一开始就执行的 - 所以当我们进入每个section时,我们知道大小为5,容量至少为5。这能正常工作是因为宏SECTION包含一个if语句,它将回调Catch来查看是否应该执行该section(部分)。通过TEST_CASE,每次运行都将执行一个叶section。其他section被跳过。下一次执行下一个部分时,依此类推,直到运行完所有的section
到目前为止一切都还好 - 这已经是一个改进的setup/teardown方法,因为到现在为止我们看到内联的setup代码并使用了堆栈。
然而,只有当我们需要执行一系列检查操作时,section的实力才会真正的显示出来。继续vector的示例,我们可能想要验证让vector的容量小于当前容量的时候,是无效的。我们可以这样做,例如:

    SECTION( "reserving bigger changes capacity but not size" ) {
        v.reserve( 10 );
        
        REQUIRE( v.size() == 5 );
        REQUIRE( v.capacity() >= 10 );
    
        SECTION( "reserving smaller again does not change capacity" ) {
            v.reserve( 7 );
            
            REQUIRE( v.capacity() >= 10 );
        }
    }

Sections可以嵌套到任意深度(仅限于堆栈大小)。每个叶section(即不包含嵌套sectionsection)将在执行任何其他叶片部分的单独路径上执行一次(因此叶片部分可能会干扰另一个叶节点)。父section的失败会阻止嵌套section运行 - 就是这样设计的。

BDD风格

如果您适当地命名测试用例和部分,您可以实现BDD样式的规范结构。
这成为一种有用的工作方式,Catch已经添加了很好的支持。场景可以使用指定的SCENARIOGIVENWHENTHEN宏,其分别映射到TEST_CASESSECTION。有关详细信息,请参阅Test cases and sections
vector示例可以调整为使用这些宏,如下所示:

SCENARIO( "vectors can be sized and resized", "[vector]" ) {

    GIVEN( "A vector with some items" ) {
        std::vector<int> v( 5 );
        
        REQUIRE( v.size() == 5 );
        REQUIRE( v.capacity() >= 5 );
        
        WHEN( "the size is increased" ) {
            v.resize( 10 );
            
            THEN( "the size and capacity change" ) {
                REQUIRE( v.size() == 10 );
                REQUIRE( v.capacity() >= 10 );
            }
        }
        WHEN( "the size is reduced" ) {
            v.resize( 0 );
            
            THEN( "the size changes but not capacity" ) {
                REQUIRE( v.size() == 0 );
                REQUIRE( v.capacity() >= 5 );
            }
        }
        WHEN( "more capacity is reserved" ) {
            v.reserve( 10 );
            
            THEN( "the capacity changes but not the size" ) {
                REQUIRE( v.size() == 5 );
                REQUIRE( v.capacity() >= 10 );
            }
        }
        WHEN( "less capacity is reserved" ) {
            v.reserve( 0 );
            
            THEN( "neither size nor capacity are changed" ) {
                REQUIRE( v.size() == 5 );
                REQUIRE( v.capacity() >= 5 );
            }
        }
    }
}

很方便的是,这些测试将在运行时报告如下:

Scenario: vectors can be sized and resized
     Given: A vector with some items
      When: more capacity is reserved
      Then: the capacity changes but not the size

进一步

为了简化我们的教程,我们将所有的代码放在一个文件中。
以此来开始是很好的 - 可以让我们更快和更容易的进入Catch。
然而当你写更多的真实世界的测试时,这显然不是真正最好的方法。
要写以下这样的代码块(或等价物):

#define CATCH_CONFIG_MAIN
#include "catch.hpp"

出现在正好的一个源文件中。使用尽可能多的额外的cpp文件(也可以叫做实现文件)进行测试,某种意义上,也是最适合您的工作方式。这些cpp文件只需要#include "catch.hpp" - 不要重复#define
实际上,将#define放在 在指定的源文件中(该文件只包含defineinclude两句代码)通常是个好主意。
不要在头文件中写你的测试!

下一步

这是一个简短的介绍,让您开始运行Catch,并指出Catch和您可能已经熟悉的其他框架之间的一些主要区别。这将让你走得很远,你现在可以继续深入了解和写一些测试。当然,还有更多需要学习的,请参阅参考部分,了解可用的内容。


Catch官网

相关文章

网友评论

      本文标题:Catch使用指南

      本文链接:https://www.haomeiwen.com/subject/gbefextx.html