一个简单的代码测试模板
作者: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) |