Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1075232
  • 博文数量: 77
  • 博客积分: 11498
  • 博客等级: 上将
  • 技术积分: 1840
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-04 11:10
文章分类

全部博文(77)

文章存档

2011年(1)

2010年(16)

2009年(5)

2008年(55)

分类: C/C++

2008-10-07 20:04:03


    一个简单的代码测试模板
    作者:tyc611.cublog.cn,2008-10-07
有时,自己写了一小段代码或一个简单的类,为了确保其正确工作,往往需要进行一些单元测试。当然,此时可以使用CppUnit这类测试工具,但有点杀鸡用牛刀的感觉,并且由于工作繁琐而容易让人气馁,进而可能打消测试的欲望。另一方面,如果要发布这些代码,也相当于要求受众也需要安装相应的测试环境。对于大的项目是可行的,但对于小东西大可不必如何费劲。我发现用一个简单的测试模板可以很好应付这类情况,只需要Copy代码,然后依样画葫芦编写测试代码即可。

测试框架大致是这样的:对于每个单元(比如一个或几个算法(函数)、一个类),对应的写一个可编译的测试单元(使用测试模板),并使用一个编译符号来控制是否进行单元测试。在测试单元中,在一个匿名名字空间中定义一个单元测试类,并把每个测试用例写成一个成员函数,然后在构造函数中调用这些成员成数。然后,在该匿名名字空间中定义一个类对象,并使用编译符号来控制是否构造该对象。由于类的定义和类对象定义都是在该匿名名字空间中,因此不会影响其它编译单元。先写一个类似assert的Assure来进行表达式断言,但是在失败时只抛出异常而不是退出程序,且在Debug和Release中均可用。然后,在每个测试用例(成员函数)中,把测试代码放在try-catch语句中,并捕获异常。

下面实现一个“不可修改”的数组模板类,并进行测试。如果需要进行测试,则在编译时定义符号SYMBOL_DO_TEST。

测试模板及一个测试实例的代码如下:

/**
* Assure.h
* @Author   Tu Yongce
* @Created  2008-1-1
* @Modified 2008-1-1
* @Version  0.1
*/

#ifndef ASSURE_H_INCLUDED
#define ASSURE_H_INCLUDED

#include
#include

class AssureException: public std::exception
{
};

#define Assure(os, x) (void)((!!(x)) || (ShowFailedMessage(os, #x, __FILE__, __LINE__), 0))

inline void ShowFailedMessage(std::ostream &os, const char* expr, const char *file, size_t line)
{
    os << "Failed: " << expr << ", file \"" << file << "\", line " << line << '\n';
    throw AssureException();
}

#define ANONYMOUS_NAMESPACE_START namespace {
#define ANONYMOUS_NAMESPACE_END   }

#endif // ASSURE_H_INCLUDED

/**
* ImmutableArray_example.cpp
* @Author   Tu Yongce
* @Created  2008-10-7
* @Modified 2008-10-7
* @Version  0.1
*/

#include
#include "ImmutableArray.h"
#include "Assure.h"

using namespace std;

ANONYMOUS_NAMESPACE_START

class UnitTest
{
private:
    ostream &m_log;
public:
    UnitTest(ostream &log): m_log(log)
    {
        m_log << "TestImmutableArray Start...\n";

        DoTest1();
        DoTest2();
        DoTest3();

        m_log << "TestImmutableArray End\n\n";
    }

private:
    void DoTest1()
    {
        m_log << "DoTest1 Start...\n";

        try {
            const size_t NUM = 0x100000;  // 2^20
            ImmutableArray arr(NUM, false);

            Assure(m_log, arr.Size() == NUM);

            for (size_t i = 0; i < NUM; ++i) {
                Assure(m_log, arr.Empty(i));
                Assure(m_log, arr.Get(i) == false);
                Assure(m_log, arr.Put(i, true));
                Assure(m_log, !arr.Empty(i));
                Assure(m_log, arr.Get(i) == true);
                Assure(m_log, !arr.Put(i, true));
            }
        }
        catch (AssureException) {
        }

        m_log << "DoTest1 End\n";
    }

    void DoTest2()
    {
        m_log << "DoTest2 Start...\n";

        try {
            const size_t NUM = 0x100000;  // 2^20
            ImmutableArray arr(NUM, true);

            Assure(m_log, arr.Size() == NUM);

            for (size_t i = 0; i < NUM; ++i) {
                Assure(m_log, arr.Empty(i));
                Assure(m_log, arr.Get(i) == true);
                Assure(m_log, arr.Put(i, false));
                Assure(m_log, !arr.Empty(i));
                Assure(m_log, arr.Get(i) == false);
                Assure(m_log, !arr.Put(i, false));
            }
        }
        catch (AssureException) {
        }

        m_log << "DoTest2 End\n";
    }

    void DoTest3()
    {
        m_log << "DoTest3 Start...\n";

        try {
            const size_t NUM = 0x100000;  // 2^20
            ImmutableArray arr(NUM, -1);

            Assure(m_log, arr.Size() == NUM);

            for (size_t i = 0; i < NUM; ++i) {
                Assure(m_log, arr.Empty(i));
                Assure(m_log, arr.Get(i) == -1);
                Assure(m_log, arr.Put(i, i));
                Assure(m_log, !arr.Empty(i));
                Assure(m_log, arr.Get(i) == i);
                Assure(m_log, !arr.Put(i, i));
            }
        }
        catch (AssureException) {
        }

        m_log << "DoTest3 End\n";
    }
};

#ifdef SYMBOL_DO_TEST
UnitTest obj(std::clog);  // do test
#endif // SYMBOL_UNIT_TEST

ANONYMOUS_NAMESPACE_END

/**
* ImmutableArray.h
* @Author   Tu Yongce
* @Created  2008-10-7
* @Modified 2008-10-7
* @Version  0.1
*/

#ifndef IMMUTABLE_ARRAY_H_INCLUDED
#define IMMUTABLE_ARRAY_H_INCLUDED

#include
#include

/*
* 不可修改数组,数组元素值一旦设定即不可修改
*/
template
class ImmutableArray
{
public:
    typedef T ValueType;

private:
    ValueType m_placeHolder;
    std::vector m_data;

public:
    /*
    * 构造一个ImmutableArray对象
    * @param n: 数组元素个数
    * @param placeHolder: 数组元素在被设定值之前的占位符
    */
    ImmutableArray(size_t n, ValueType placeHolder): m_placeHolder(placeHolder),
        m_data(n, placeHolder)
    { }

    /*
    * 在数组的指定位置处存储值
    * @param index: 指示存储位置的数组下标,必须在范围[0, n)内
    * @param value: 待存储的值,不能与构造函数使用的参数值placeHolder相同
    * @return: 如果指定位置已经有值,则返回false,并且放弃存储操作;否则返回true
    */
    bool Put(size_t index, ValueType value)
    {
        assert(index < m_data.size());
        assert(value != m_placeHolder);

        if (m_data[index] != m_placeHolder)
            return false;

        m_data[index] = value;

        return true;
    }

    /*
    * 读取数组指定存储位置处的值
    * @param index: 指示存储位置的数组下标,必须在范围[0, n)内
    * @return: 如果指定位置已经有值,则返回该值;
    *          否则,返回构造函数使用的参数值placeHolder
    */
    ValueType Get(size_t index) const
    {
        return m_data[index];
    }

    /*
    * 查询数组指定位置处是否为空(还未存储值)
    * @param index: 指示存储位置的数组下标,必须在范围[0, n)内
    * @return: 如果指定位置有值,则返回false;否则返回true
    */
    bool Empty(size_t index) const
    {
        return m_data[index] == m_placeHolder;
    }

    /*
    * 返回数组的元素个数(即返回构造的参数n的值)
    */
    size_t Size() const
    {
        return m_data.size();
    }
};

/*
* 模板类ImmutableArray针对bool类型的特化类
* @note: 能够压缩存储空间,有效节约使用的内存资源
*/
template<>
class ImmutableArray
{
public:
    typedef bool ValueType;

private:
    typedef unsigned char uint8_t;
    size_t m_size;
    bool m_placeHolder;
    std::vector m_data;

public:
    ImmutableArray(size_t n, bool placeHolder) : m_size(n),
        m_placeHolder(placeHolder),
        m_data((n + 7) / 8, (placeHolder ? 0xFF : 0x00))
    { }

    bool Put(size_t index, bool value)
    {
        assert(index < m_size);
        assert(value != m_placeHolder);

        bool tag = (m_data[index / 8] & (uint8_t(0x01) << (index % 8))) != 0;
        if (tag != m_placeHolder)
            return false;

        // 指定位的0,1互换
        m_data[index / 8] ^= (uint8_t(0x01) << (index % 8));

        return true;
    }

    bool Get(size_t index) const
    {
        return (m_data[index / 8] & (uint8_t(0x01) << (index % 8))) != 0;
    }

    bool Empty(size_t index) const
    {
        bool tag = (m_data[index / 8] & (uint8_t(0x01) << (index % 8))) != 0;
        return tag == m_placeHolder;
    }

    size_t Size() const
    {
        return m_size;
    }
};

/**
* example.cpp
* @Author   Tu Yongce
* @Created  2008-10-7
* @Modified 2008-10-7
* @Version  0.1
*/

int main()
{
}


上面的测试模板有一个问题,那就是“UnitTest obj(std::clog);”。由于测试对象是一个文件域静态对象,它的构造函数会在main函数执行之前进行。而std::clog也是一个全局对象,它也在main函数之前被构造。但这两个静态对象的构造顺序是不确定的,所以引号中的代码不一定能正常运行(当然,目前在我机器上还运行良好)。要解决此问题,可以写一个日志类,并且实现Singleton模式,然后测试类获取日志类的实例。懒得写了,有兴趣的自己写个玩吧 :^)

阅读(1391) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~