精选文章

Android下使用TCPDUMP抓包Wireshark分析数据 如果想分析Android下某个APP的网络数据交互,需要在Android手机上抓包,最常用的抓包工具非tcpdump莫属,用tcpdump生成Wireshark识别的pcap文件,然后将pcap文件下载到电脑上,用电脑上的Wireshark加载pcap文件,通过Wireshark分析tcpdump抓取的数据。...

继续阅读

Mac下部署Android开发环境附加NDK 作为开发者,我们深有体会,不管是进行什么开发,为了部署开发环境,我们往往需要折腾很长时间、查阅很多资料才能完成,而且这次折腾完了,下次到了另一台新电脑上又得重新来过,整个部署过程记得还好,要是不记得又得重新开始,而且遇到Android这种GFW阻隔了开发资源下载链接的环境部署,又尤其浪费时间。所以这也是我写下这篇教程的初衷跟动力源泉,希望大家参考了这篇教程以后可以轻轻松松在Mac系统下将Android环境部署好。...

继续阅读

稍顯嚴肅的台中 坦白說,留在腦海中的台中影像並不多,來台灣之前在Booking上只訂到了台中的一家青旅,第一次住青旅有些不習慣,幹什麼都放不開。 同屋的一個男生是台灣人,不過一年中四分之三的時間在上海跟北京,這麼說來跟我還是比較有共同話題的。得之我準備花15天的時間環島,覺得太倉促了,他們大學時期花一個半月的時間也不見得能將台灣島給逛完。我只能無奈地表示,兩岸允許的簽證時間有限,自己的空閒時間更有限,只能用打卡式的旅行了,我深知正真地旅行應該慢下來,融入當地的環境,感受他們的風土人情,但第一次只能這樣作罷,以後換成民進黨上台,形勢會變成怎樣還不得而知,能否再過來還是個未知數。而我一向信奉的人生格言是秉燭夜遊,活在當下,所以,理解自己吧。...

继续阅读

為之留戀的新竹 來新竹之前本沒有對她有過高的期待,慢慢對她加分要從桃園火車站出發前往新竹開始。 在桃園火車站的候車月台上,有醒目的旅遊資料發放處,這上面的擺放的全是新竹的旅遊宣傳資料,關鍵的是資料做得非常簡潔易懂,而接下來一天的新竹之行就全部是依據這份寶典的指引來完成的。...

继续阅读

從桃園開始台灣之行 初到台灣恰逢華夏銀行系統升級,特意準備的華夏銀聯卡在桃園機場沒能派上用場,只好用建行在機場5000塊,算下來是很不划算的,但是沒辦法,誰叫我出機場就得花錢呢。 從機場打車到桃園的酒店,花了將近六百塊新台幣,到酒店時五點多,天已經漸亮了,洗漱完等到七點吃過早餐就開始補覺囉,一覺醒來已是中午,帶著換下來的衣服外出找自助洗衣店,順便覓食。...

继续阅读

  • Prev
  • Next

单元测试框架CppTest

4

文章分类 : C++, Linux, 应用与编程, 教程

一、CppTest简介

CppTest 是一个简单、轻便的 C++ 单元测试框架,它具有实用性与可扩展性,支持多种输出格式,添加新的测试用例极其方便。CppTest 最大的优点是容易理解、便于掌握和使用。    

二、CppTest部署原理

CppTest 作为一个动态库被调用,要使用 CppTest 进行测试,首先需要部署 CppTest,编译 CppTest 动态库。

在 http://sourceforge.net/projects/cpptest/ 上找到 CppTest 的最新源码包,将其下载并解压,解压后将生成一个名为 cpptest-1.1.1 的目录,进入该目录,阅读该其中的 INSTALL 文件,它告诉我们详细的安装步骤:

1、执行配置脚本:./configure。如果报错,需要根据提示解决相应的错误方能继续。如果成功,最终将生成 Makefile 文件。

2、执行 make 命令编译源代码。

3、通常,我们可以执行 make check 命令来检查编译是否成功。

4、执行 sudo make install 命令,安装 CppTest 极其相关的动态库、头文件等。注意,由于该命令需要将相关文件拷贝到系统目录下,所以务必以管理员权限执行。

5、我们可以使用 make clean 来轻度清理生成文件,使用 make distclean 来清理包括 Makefile 在内的所有生成文件。

三、CppTest应用原理

1、CppTest 中的宏

CppTest 提供几个有用的宏,可以在测试客户机源代码的实际方法中使用它们。这些宏在 cpptest-assert.h 中定义,cpptest.h 包含这个头文件。下面讨论一些宏和可能的用例。

1)TEST_ADD (function)

这个宏用于注册需要测试的函数接口(如清单1),该 function 必须指定为无返回值无参数类型的函数指针。function 函数内部可以添加需要测试的代码或其他 CppTest 宏。

清单 1.使用 TEST_ADD 宏的客户机代码

#include "cppTest.h"

class myTest : public Test::Suite {
    void function1_to_test_some_code( );
    void function2_to_test_some_code( );

    myTest( ) {
        TEST_ADD (myTest::function1_to_test_some_code);
        TEST_ADD (myTest::function2_to_test_some_code);
    }
};

2)TEST_FAIL (message)

这个宏无条件地产生失败(如清单2)。可以使用这个宏的典型情况是处理客户机函数的结果。如果结果不符合任何期望的结果,那么抛出一个包含消息的异常。当遇到 TEST_FAIL 宏时,不再执行单元测试中的其他代码。

清单 2. 使用 TEST_FAIL 宏的客户机代码

void myTestSuite::unitTest1 ( ) {
    int result = usercode( );
    switch (result) {
        case 0: // Do Something
        case 1: // Do Something
        …
        default: TEST_FAIL (“Invalid result\n”);
    }
}

3)TEST_ASSERT (expression)

这个宏与 C 语言标准库中的断言函数相似,但是 TEST_ASSERT 可以在调试和版本构建时起作用。如果表达式的结果是 False,就指出一个错误。清单 3 给出这个宏的内部实现。

清单 3. TEST_ASSERT 宏实现

#define TEST_ASSERT(expr) \
{ \
    if (!(expr)) \
    { \
        assertment(::Test::Source(__FILE__, __LINE__, #expr)); \
        if (!continue_after_failure()) return; \
    } \
}

4)TEST_ASSERT_MSG (expression, message)

这个宏与 TEST_ASSERT 相似,只是在断言失败时在输出中显示消息而不是表达式。下面是包含和不包含消息的宏示例。清单 4 显示遇到这两个宏时的输出。

TEST_ASSERT (1 + 1 == 0);
TEST_ASSERT_MSG (1 + 1 == 0, “Invalid expression”);

清单 4. TEST_ASSERT 和 TEST_ASSERT_MSG 宏的输出

Test: compare
Suite: CompareTestSuite
File: /home/trevor/linux/mytest.cpp
Line: 91
Message: 1 + 1 == 0

Test: compare
Suite: CompareTestSuite
File: /home/trevor/linux/mytest.cpp
Line: 92
Message: Invalid Expression

5)TEST_ASSERT_DELTA (expression1, expression2, delta)

如果 expression1 和 expression2 的差超过了 delta,就抛出异常。这个宏在 expression1 和 expression2 是浮点数的情况下尤其有用;例如,根据实际采用的舍入方法不同,4.3 可能存储为 4.299999 或 4.300001,因此在进行比较时需要 delta。另一个示例是测试操作系统 I/O 的代码:打开文件花费的时间不可能每次都相同,但是必须在一定的范围内。

6)TEST_ASSERT_DELTA_MSG (expression, message)

这个宏与 TEST_ASSERT_DELTA 宏相似,只是在断言失败时还显示消息。

7)TEST_THROWS (expression, exception)

这个宏检验表达式并期望遇到某种异常。如果没有捕捉到异常,就触发断言。注意,对表达式的实际值并不进行任何测试;要测试的是异常。请考虑清单 5 中的代码。

清单 5. 处理整数异常

class myTest1 : public Test::Suite {
    …
    void func1( ) {
        TEST_THROWS (userCode( ), int);
    }
    public:
        myTest1( ) { TEST_ADD(myTest1::func1); }
};

float userCode( ) throws(int) {
    …
    throw 1;
    return 0.1;
}

注意,userCode 例程的返回类型并不重要:它也可以是实型或整型。因为这里的 userCode 无条件地抛出 int 类型的异常,所以这个测试会顺利通过。

8)TEST_THROWS_MSG (expression, exception, message)

这个宏与 TEST_THROWS 相似,只是它输出消息而不是表达式。下面是包含和不包含消息的宏示例。清单 6 显示遇到这两个宏时的输出。

TEST_THROWS(userCode( ), int);
TEST_THROWS_MSG(userCode( ), int, “No expected exception of type int”);

清单 6. TEST_THROWS 和 TEST_THROWS_MSG 宏的输出

Test: func1
Suite: myTest1
File: /home/trevor/linux/mytest.cpp
Line: 24
Message: userCode()

Test: func2
Suite: myTest1
File: /home/trevor/linux/mytest.cpp
Line: 32
Message: No expected exception of type int

9)TEST_THROWS_ANYTHING (expression)

有时候,客户机例程根据具体情况抛出不同类型的异常。可以使用 TEST_THROWS_ANYTHING 宏处理这种情况,这个宏不需要指定期望的异常类型。只要在执行客户机代码之后捕捉到异常,就不会触发断言。

2、CppTest 的测试套件

CppTest 的测试套件是单元测试用于测试源代码的特定部分。形式最简单的测试套件包含一组测试其他 C/C++ 代码的 C/C++ 函数。CppTest 在 Test 名称空间中定义一个名为 Suite 的类,它提供基本的测试套件功能。用户定义的测试套件必须扩展这个功能,定义作为实际单元测试运行的函数。清单 7 定义一个名为 myTest 的类,其中有两个函数,分别测试一段源代码。TEST_ADD 是用于注册测试的宏。

清单 7. 扩展基本的 Test::Suite 类

#include “cppTest.h”

class myTest : public Test::Suite {
    void function1_to_test_some_code( );
    void function2_to_test_some_code( );

    myTest( ) {
        TEST_ADD (myTest::function1_to_test_some_code);
        TEST_ADD (myTest::function2_to_test_some_code);
    }
};

3、扩展测试套件

我们可以很容易地扩展测试套件的功能,创建测试套件的层次结构。每个测试套件可能测试不同的代码部分,例如分别测试编译器的解析、代码生成和代码优化,创建层次结构有助于更好地管理。清单 8 演示如何创建这样的层次结构。

清单 8. 创建测试套件层次结构

#include “cppTest.h”

class unitTestSuite1 : public Test::Suite { … }
class unitTestSuite2 : public Test::Suite { … }

class myTest : public Test::Suite {
    myTest( ) {
        add (std::auto_ptr<Test::Suite>(new unitTestSuite1( )));
        add (std::auto_ptr<Test::Suite>(new unitTestSuite2( )));
    }
};

add 方法属于 Suite 类。清单 8 给出它的原型(取自头文件 cpptest-suite.h)。

清单 8. Suite::add 方法声明

class Suite
{
public:
    …
    void add(std::auto_ptr<Suite> suite);
    ...
} ;

四、CppTest应用场景

CppTest常应用于白盒测试,可以检测一段代码执行所消耗的时间,结合 CppTest 提供的一些宏的使用,可以跟踪指定变量是否得到预期的值,还可以跟踪程序是否出现异常。

五、CppTest应用举例

#include <cstdlib>
#include <cstring>
#include <iostream>
#include "cpptest.h"

using namespace std;

// 测试无条件失败断言,即测试 TEST_FAIL 宏
//
class FailTestSuite : public Test::Suite
{
public:
    FailTestSuite()
    {
        TEST_ADD(FailTestSuite::success)
        TEST_ADD(FailTestSuite::always_fail)

    }

private:
    void success() {}

    void always_fail()
    {
        // 始终失败
        //
        TEST_FAIL("unconditional fail");
    }
};

// 测试比较断言
//
class CompareTestSuite : public Test::Suite
{
public:
    CompareTestSuite()
    {
        TEST_ADD(CompareTestSuite::success)
        TEST_ADD(CompareTestSuite::compare)
        TEST_ADD(CompareTestSuite::delta_compare)
    }

private:
    void success() {}

    void compare()
    {
        // 表达式为真所以该断言成功
        //
        TEST_ASSERT(1 + 1 == 2)

        // 表达式为假所以该断言失败
        //
        TEST_ASSERT(0 == 1);
    }

    void delta_compare()
    {
        // 0.5 与 0.7 的差值小于 0.3,所以断言成功
        //
        TEST_ASSERT_DELTA(0.5, 0.7, 0.3);

        // 0.5 与 0.7 的差值大于 0.1,所以断言失败
        //
        TEST_ASSERT_DELTA(0.5, 0.7, 0.1);
    }
};

// 测试抛出异常断言
//
class ThrowTestSuite : public Test::Suite
{
public:
    ThrowTestSuite()
    {
    TEST_ADD(ThrowTestSuite::success)
    TEST_ADD(ThrowTestSuite::test_throw)
    }

private:
    void success() {}

    void test_throw()
    {
        // 因为没有函数抛出异常,所以抛出异常断言将失败
        //
        TEST_THROWS_MSG(func(), int, "func() does not throw, expected int exception")
        TEST_THROWS_MSG(func_no_throw(), int, "func_no_throw() does not throw, expected int exception")
        TEST_THROWS_ANYTHING_MSG(func(), "func() does not throw, expected any exception")
        TEST_THROWS_ANYTHING_MSG(func_no_throw(), "func_no_throw() does not throw, expected any exception")

        // 因为没有函数抛出异常,所以无抛出异常断言将成功
        //
        TEST_THROWS_NOTHING(func())
        TEST_THROWS_NOTHING(func_no_throw())

        // 因为 func_throw_int() 抛出一个 int 异常,所以抛出断言将成功
        //
        TEST_THROWS(func_throw_int(), int)
        TEST_THROWS_ANYTHING(func_throw_int())

        // 因为 func_throw_int() 抛出一个 int (而不是 float) 异常,所以该抛出异常断言将失败,无抛出异常断言也将失败
        //
        TEST_THROWS_MSG(func_throw_int(), float, "func_throw_int() throws an int, expected a float exception")
        TEST_THROWS_NOTHING_MSG(func_throw_int(), "func_throw_int() throws an int, expected no exception at all")
    }

    void func() {}
    void func_no_throw() throw() {}
    void func_throw_int() throw(int) { throw 13; }
};

enum OutputType
{
    Compiler,
    Html,
    TextTerse,
    TextVerbose
};

static void
usage()
{
    cout << "usage: mytest [MODE]\n"
        << "where MODE may be one of:\n"
        << " --compiler\n"
        << " --html\n"
        << " --text-terse (default)\n"
        << " --text-verbose\n";
    exit(0);
}

static auto_ptr<Test::Output>
cmdline(int argc, char* argv[])
{
    if (argc > 2)
    usage(); // 内部直接退出程序,将不再返回

    Test::Output* output = 0;

    if (argc == 1)
        output = new Test::TextOutput(Test::TextOutput::Verbose);
    else
    {
        const char* arg = argv[1];
        if (strcmp(arg, "--compiler") == 0)
            output = new Test::CompilerOutput;
        else if (strcmp(arg, "--html") == 0)
            output = new Test::HtmlOutput;
        else if (strcmp(arg, "--text-terse") == 0)
            output = new Test::TextOutput(Test::TextOutput::Terse);
        else if (strcmp(arg, "--text-verbose") == 0)
            output = new Test::TextOutput(Test::TextOutput::Verbose);
        else
        {
            cout << "invalid commandline argument: " << arg << endl;
            usage(); // 内部直接退出程序,将不再返回
        }
    }

    return auto_ptr<Test::Output>(output);
}

// 测试程序主函数
//
int
main(int argc, char* argv[])
{
    try
    {
    // 演示使用多个测试套件的功能
    //
    Test::Suite ts;
    ts.add(auto_ptr<Test::Suite>(new FailTestSuite));
    ts.add(auto_ptr<Test::Suite>(new CompareTestSuite));
    ts.add(auto_ptr<Test::Suite>(new ThrowTestSuite));

    // 运行测试
    //
    auto_ptr<Test::Output> output(cmdline(argc, argv));
    ts.run(*output, true);

    Test::HtmlOutput* const html = dynamic_cast<Test::HtmlOutput*>(output.get());
    if (html)
        html->generate(cout, true, "MyTest");
    }
    catch (...)
    {
        cout << "unexpected exception encountered\n";
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

六、CppTest测试报告

CppTest特有一个格式化输出器,它专门负责生成测试报告。

格式化输出器背后的思想是,可能需要不同格式的回归测试运行报告:文本、HTML 等等。因此,run 方法本身并不转储结果,而是接受一个 Output 类型的对象,它负责显示结果。在 CppTest 中可以使用三种输出格式化器:

Test::TextOutput。这是所有输出处理器中最简单的一种。显示模式可以是详细或简洁。
Test::CompilerOutput。按照与编译器构建日志相似的方式生成输出。
Test::HtmlOutput。生成 HTML 输出。

在默认情况下,这三种格式化器都把输出发送到 std::cout。前两种格式化器的构造器接受一个 std::ostream 类型的参数,它指定输出的目标,例如可以把输出转储到文件中以便进一步分析。还可以创建输出格式化器的定制版本。为此,只需从 Test::Output 派生出用户定义的格式化器。可以通过清单 9 – 12 中的代码理解不同的输出格式。

清单 9. 以简洁模式运行 TEST_FAIL 宏

#include “cppTest.h”

class failTest1 : public Test::Suite {
    void always_fail( ) {
    TEST_FAIL (“This always fails!\n”);
}
public:
    failTest1( ) { TEST_ADD(failTest1::always_fail); }
};

int main ( ) {
failTest1 test1;
    Test::TextOutput output(Test::TextOutput::Terse);
    return test1.run(output) ? 1 : 0;
}

清单 10. 简洁输出只显示失败数

failTest1: 1/1, 0% correct in 0.000000 seconds
Total: 1 tests, 0% correct in 0.000000 seconds

清单 11. 以详细模式运行 TEST_FAIL 宏

#include “cppTest.h”

class failTest1 : public Test::Suite {
    void always_fail( ) {
    TEST_FAIL (“This always fails!\n”);
}
public:
    failTest1( ) { TEST_ADD(failTest1::always_fail); }
};

int main ( ) {
    failTest1 test1;
    Test::TextOutput output(Test::TextOutput::Verbose);
    return test1.run(output) ? 1 : 0;
}

清单 12. 详细输出显示文件/行信息、消息、测试套件信息等等

failTest1: 1/1, 0% correct in 0.000000 seconds
Test: always_fail
Suite: failTest1
File: /home/trevor/linux/mytest.cpp
Line: 5
Message: "This always fails!\n"

Total: 1 tests, 0% correct in 0.000000 seconds

清单 13. 用编译器式输出格式运行 TEST_FAIL 宏

#include “cppTest.h”

class failTest1 : public Test::Suite {
    void always_fail( ) {
    TEST_FAIL (“This always fails!\n”);
}
public:
    failTest1( ) { TEST_ADD(failTest1::always_fail); }
};

int main ( ) {
    failTest1 test1;
    Test::CompilerOutput output;
    return test1.run(output) ? 1 : 0;
}

清单 14. GCC 风格的编译器输出

/home/trevor/linux/mytest.cpp:5: “This always fails!\n”

打印 HTML 风格的输出格式使用 HtmlOutput。注意,HTML 格式化器并不在构造器中接受文件句柄,而是依靠 generate 方法。generate 方法的第一个参数是一个 std::ostream 类型的对象,它的默认值是 std::cout。可以使用文件句柄把日志重定向到其他地方。清单 15 提供一个示例。

清单 15. HTML 风格的输出格式

#include <ostream>

int main ( ) {
    failTest1 test1;
    std::ofstream ofile;
    ofile.open("test.log");
    Test::HtmlOutput output( );
    test1.run(output);
    output.generate(ofile);
    return 0;
}

清单 16. 生成 HTML 格式的输出片段并存储到 test.log 文件中

…
<table summary="Test Failure">
<tr>
<td style="width:15%">Test</td>
<td>failTest1::always_fail</td>
</tr>
<tr>
<td style="width:15%">File</td>
<td>/home/arpan/test/mytest.cpp:18</td>
</tr>
<tr>
<td style="width:15%">Message</td>
<td>"This always fails!\n"</td>
</tr>
</table>
…

除非注明,文章均为CppLive 编程在线原创,转载请注明出处,谢谢。

本文地址:https://www.cpplive.com/html/1784.html

评论 (4)

  • 捣乱 says:

    我也转web咯,linux少不了,暑假好好玩一下。什么gcc,gdb,makefile etc..以前只是玩过一点,简单的应用规则,看来要好好深入一下了

  • 这里因为你的留言而存在!!!

    You must be logged in to post a comment.