文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

获取Catch

最简单的获取Catch的方法是下载最新版的 单个头文件。头文件由一组独立的头文件合并而成,但是它仍是在头文件中的正常源代码。

Catch的所有资源,包括测试项目,文档等都在Github上,网址:http://catch-lib.net

Catch要放在哪里?

Catch只是一个头文件。你只需要将它放在项目能够找到的文件位置即可,不论是你设置的搜索路径的位置,还是直接在你的项目中!对于想要使用Catch进行测试的开源项目来说是一个很好的选择。 关于这一点的细节可以查看我的博客

第 1 段(可获 1.69 积分)

本教程假设我们已经正常获取了Catch头文件(或include文件夹)——但如果必要的话,您可能需要使用文件夹名称来前缀它。

编写测试

从一个简单的例子开始。比如你写了一个计算阶乘的函数,现在我们需要测试它(暂时撇开TDD)。

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

为了保持教程简洁,我们将所有代码放在一个文件中(之后我们会给出测试文件的结构)

第 2 段(可获 1.11 积分)
#define CATCH_CONFIG_MAIN  // 通知Catch提供主函数main()-只能在一个cpp文件中定义
#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 );
}

这段代码能够编译成一个完整的可执行文件,能够在命令行中执行。如果在无参数时运行,它将执行所有测试用例(在这种情况下只有一个),报告任何错误,通过和未通过测试的总数以及和失败测试的个数(当你只是想知道它是否正常工作时这是非常有用的)。

第 3 段(可获 0.85 积分)

运行如上代码,所有测试用例都会通过。但这真的是对的吗?其实这里还有一个bug。实际上教程的第一版代码存在bug!所以它并不完整(感谢Daryle Walker指出@CTMacUser)。

bug是什么呢?如果是0的阶乘呢?0的阶乘是0(你需要知道并记住)。

将此测试增加到测试用例中:

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 );
}
第 4 段(可获 1.11 积分)

下面我们得到如下错误:

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

注意到我们实际得到的Factorial(0)的返回值为0——这里使用了==操作符进行表示(虽然不符合逻辑),但我们能够立刻看出来问题是什么。

下面我们对阶乘计算函数进行修改:

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

现在所有测试都能够通过。

当然还有更多的地方需要修改。例如当返回值超过无符号整型的范围时会出现问题,在计算阶乘结果时,这种情况很快就会出现。你可以增加这种情况的测试用例并决定如何处理它们。对此我们暂时不做讲解。

第 5 段(可获 1.45 积分)

我们做了什么?

以上示例虽然很简单,但也能简单说明Catch是如何使用的问题。在继续新的内容之前,让我们先思考以下几点:

  1. 我们唯一需要做的事情就是: #define一个标识符和 #include一个头文件。此时main()函数已经能够对命令行做出响应。注意只能在一个实现文件中包含#define,一旦你需要在多个文件中加入单元测试,你只需要加入 #include "catch.hpp" 。当然如果你能够做到只在一个实现文件中加入 #define CATCH_CONFIG_MAIN 和#include "catch.hpp"是最好的了。你也可以使用自己的main函数并运行Catch(参考提供自己的main()函数)。
  2. 使用TEST_CASE宏引入测试用例。这个宏需要一个或两个参数,允许测试名自定义以及可选的一个或多个标签(其他请查看测试用例和)。测试用例的名字需要是唯一的,你可以通过指定一个通配符或标签表达式进行测试设置。请在命令行文档查看更多运行测试的信息。
  3. 参数的名字和标签只是string类型。我们不一定要声明函数或方法或者显示地注册测试用例。后台会自动生成函数名并使用静态注册类进行注册。通过抽象函数名我们可以任意命名我们的测试。
  4. 使用 REQUIRE宏进行测试断言的编写。使用C/C++语法表示自然情况 而不是为每种情况都有一个宏。在后台中对表达模版进行简单的设置以区分表达式的左侧和右侧,以方便我们能够在测试报告中显示值。稍后我们还会看到其他断言宏,但由于如上所属技术,断言宏的数量已经大量减少。
第 6 段(可获 4.1 积分)

测试用例和 sections

大多数测试框架都有一个基于类的固定机制。也就是测试用例和类中的方法相联系,setup和teardown操作在setup() 和 teardown()方法中实现(对应C++等中的构造函数/析构函数)。

Catch虽然支持如上方法但仍存在一些问题。当代码必须使用特定方式进行分离时可能会导致一些问题。一般来说一个方法只有一对setup/teardown,但有些时候setup需要有轻微的不同,或者你可能需要不同层次的setup(不同层次的概念将稍后在教程中说明)。也就是James Newkirk提出的 类似问题 ,他带领队伍搭建NUnit,从零开始搭建xUnit

第 7 段(可获 1.83 积分)

Catch采用不同的(对比NUnit和xUnit) 更适合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 );
    }
}
第 8 段(可获 0.39 积分)

 

每个SECTION 都会从头开始执行TEST_CASE,当我们进入到每个section时,我们已经知道v的大小是5且容量大于等于5。为了确保达到这些这些要求,我们使用REQUIRE 强制进行限制。他们的工作机制是:SECTION中包含if语句,并将结果返回到Catch中以确定哪个section将会执行。TEST_CASE每运行一次会执行一个section,其他section直接跳过。下一次则执行下一个section,如此反复直到没有新的section。

第 9 段(可获 1.24 积分)

到目前为止一切良好——这已经对setup/teardown方法进行了提升,我们可以看到在setup方法的代码里可以内联和使用堆栈。

section的作用我们已经看到,但是我们需要去执行一系列的检查和操作。继续以vector为例,我们也许想要验证保留比当前vcector容器容量更小的操作不会造成任何改变。很自然我们能够这么做:

    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 );
        }
    }

 

第 10 段(可获 0.95 积分)

Section可以无限制的嵌套(只受栈大小的限制)。在每一个子section的单独执行路径中,每个子section(i.e. 没有嵌套section的section)只被执行一次(因此每个子section不会相互干扰)。如果一个section执行失败,它的子section也不会运行,当然这也是它的基本思想。

BDD风格

如果你对测试用例和section进行合适的命名,你能够实现一个BDD风格的规范结构。这成为一种有效的工作方法,自从Catch加入第一类支持功能后。可以使用SCENARIO, GIVEN, WHENTHEN 宏指令进行场景描述,这分别与 TEST_CASEs 和SECTIONs相对应。想要得到更多细节请参考 Test cases and sections文档。

第 11 段(可获 1.58 积分)

向量样本可以使用如下宏代码进行调整:

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 );
            }
        }
    }
}

 

第 12 段(可获 0.15 积分)

测试的运行报告如下所示:

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入门。而在现实中你会写更多的测试,这种方法就不是最好的了。

使用Catch需要添加如下代码块(或等价代码):

#define CATCH_CONFIG_MAIN
#include "catch.hpp"
第 13 段(可获 0.84 积分)

出现在唯一一个文件中。根据测试需求会存在许多附加的cpp文件(或任何需要调用实现文件的东西),分区域是完成需求最好的方式。此时每一个附加文件只需要#include "catch.hpp" - 千万不要重复#define!

实际上把包含 #define 的代码块放到它自身的源文件中  是一个很好的解决方法。

不要在你的头文件中写测试哦!

下一步

这是Catch运行的一些简单介绍,并给出Catch和一些你知道的其他测试框架的关键不同点。这能够保证你能够利用Catch写一些测试并运行。

第 14 段(可获 1.64 积分)

当然还有更多的东西需要去学习——但部分文档仍未完成。更多内容请参阅已完成的参考部分

第 15 段(可获 0.39 积分)

文章评论