在大多数情况下,我们写了一个函数,为了验证这个函数的正确性,我们还需要写很多的测试代码。可用于C/C++单元测试的框架有很多,什么cpptest, gtest等等不计其数。他们很强大,可以很方便的拿来使用到我们的项目中。但是有的时候,我的项目很小,或者说我的函数功能很少,小到运行的时间比框架启动的时间都还要短,这个时候我的目的很简单,就是要以最简单的形式,加入几行代码测试功能即可,如下:
TEST(func1());
TEST(func2());
...
TEST(func3());
毕竟大多数程序员所在的公司都没有或很少有专门的白盒测试人员,几乎都要自己写测试代码。cpptest,gtest虽然也很简单,但是还是要编译,导入,并且会在系统中耗掉一部分时间。好比共享单车一样,我就是想现在骑车出去耍一圈,我马上,立刻就要骑,然而你要我花费一点时间去借一辆自行车,这个时间我不愿意花,等我找到的时候或许我激情也没有了。
在我的项目中,有时候创建一个C++工程就仅仅是为了写一个算法而已,这个时候如果能简单到导入一个头文件,加几个宏定义,就能完成测试,那该多是多美好的事情。尽管写很多 if (condition) then 这样的语句很简单,但打印出来的效果实在太难看,也很耗费时间。在此基础上,做了一个小型的测试代码封装,只需要包含两个文件,写几行宏定义即可完成代码测试。
.h头文件:
#ifndef CPPTEST_H
#define CPPTEST_H
// This is a simple test-driver framework for C++
/*
* Example:
*
* TEST_SUIT_BEGIN;
*
* TEST_UNIT_BEGIN("function_test1");
* TEST_CHECK(xpod_common::equal(10.0, 10-0.2, 0.1), "10.0, 10-0.2, 0.1");
* TEST_CHECK(xpod_common::equal(10.0, 10-0.1, 0.1), "10.0, 10-0.1, 0.1");
* TEST_CHECK(xpod_common::equal(10.0, 10-0.0, 0.1), "10.0, 10-0.0, 0.1");
* TEST_UNIT_END;
*
* TEST_UNIT_BEGIN("function_test2");
* TEST_CHECK(xpod_common::equal(10.0, 10-0.2, 0.1), "10.0, 10-0.2, 0.1");
* TEST_CHECK(xpod_common::equal(10.0, 10-0.1, 0.1), "10.0, 10-0.1, 0.1");
* TEST_CHECK(xpod_common::equal(10.0, 10-0.0, 0.1), "10.0, 10-0.0, 0.1");
* TEST_UNIT_END;
*
* TEST_SUIT_END;
*
*/
#define TEST_SUIT_BEGIN TSuit::instance()->begin()
#define TEST_SUIT_END TSuit::instance()->end();
#define TEST_UNIT_BEGIN(name) \
{ \
TUnit unit(name); \
unit.begin()
#define TEST_CHECK(condition, msg) \
unit.testCheck(condition, msg)
#define TEST_UNIT_END \
unit.end(); \
TSuit::instance()->addNumAll(unit.numAll()); \
TSuit::instance()->addNumOK(unit.numOK()); \
TSuit::instance()->addUnit(); \
}
#include <ctime>
#include <string>
using namespace std;
class TUnit
{
public:
TUnit(const char* name):_name(name){}
unsigned int numOK(){return _numOK;}
unsigned int numAll(){return _numAll;}
void testCheck(bool bCheck, const char* msg);
void begin();
void end();
private:
unsigned int _numOK = 0;
unsigned int _numAll = 0;
string _name;
};
class TSuit
{
public:
static TSuit* instance();
public:
void addNumAll(unsigned int count){_numAll+=count;}
void addNumOK(unsigned int count){_numOK+=count;}
void addUnit(){++_numUnit;}
void begin();
void end();
private:
TSuit(){}
clock_t _tstart;
unsigned int _numOK = 0;
unsigned int _numAll = 0;
unsigned int _numUnit = 0;
};
#endif // CPPTEST_H
.cpp源文件:
#include "cpptest.h"
#include <windows.h>
string date_time()
{
time_t rawtime;
time(&rawtime);
struct tm * timeinfo = localtime (&rawtime);
char buffer[256] = {0};
strftime(buffer, 256, "[%Y-%m-%d %H:%M:%S]", timeinfo);
return string(buffer);
}
void printWarning(const char* msg)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), BACKGROUND_RED|BACKGROUND_GREEN);
printf("%s\n", msg);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);
}
void printError(const char* msg)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), BACKGROUND_RED);
printf("%s\n", msg);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);
}
void printResult(bool bOK) {
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), bOK?BACKGROUND_GREEN:BACKGROUND_RED);
printf(bOK ? "[ OK ]" : "[ FAILED ]");
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);
printf(" ");
}
void TUnit::testCheck(bool bCheck, const char* msg)
{
printResult(bCheck);
printf("%s\n", (msg!=nullptr)?msg:" ");
_numOK += bCheck ? 1 : 0;
_numAll += 1;
}
void TUnit::begin()
{
printf("[ %s ]\n-----------------------------------------------------\n", _name.data());
}
void TUnit::end()
{
printf("-----------------------------------------------------\nTotal:%d, Success:%d, Failed:%d\n\n", _numAll, _numOK, _numAll-_numOK);
}
TSuit *TSuit::instance()
{
static TSuit suit;
return &suit;
}
void TSuit::begin()
{
_numOK = 0;
_numAll = 0;
_numUnit = 0;
_tstart = clock();
printf("Test Suit Begin %s\n-----------------------------------------------------\n\n", date_time().data());
}
void TSuit::end()
{
printf("Test Suit End %s\n-----------------------------------------------------\n", date_time().data());
printf("[Test Result] Unit:%d, Total:%d, Success:%d, Failed:%d, CostTime:%.2f s\n",
_numUnit, _numAll, _numOK, _numAll-_numOK, (clock()-_tstart)/1000.0f);
}
两个文件加起来不过150多行程序,仅定义一个单元测试TUnit和TSuit类,前者用来记录一个单元测试的结果情况,后者用来记录整体测试结果,在Windows控制台支持彩色打印效果,还有执行测试的全部时间。
下面为执行测试的代码:
void test_unit()
{
TEST_UNIT_BEGIN("123");
TEST_CHECK(xpod_common::equal(10.0, 10-0.2, 0.15), "10.0, 10-0.2, 0.15");
TEST_CHECK(xpod_common::equal(10.0, 10-0.1, 0.15), "10.0, 10-0.1, 0.15");
TEST_CHECK(xpod_common::equal(10.0, 10-0.0, 0.15), "10.0, 10-0.0, 0.15");
TEST_CHECK(xpod_common::equal(10.0, 10+0.1, 0.15), "10.0, 10+0.1, 0.15");
TEST_CHECK(xpod_common::equal(10.0, 10+0.2, 0.15), "10.0, 10+0.2, 0.15");
TEST_UNIT_END;
}
int main(int argc, char *argv[])
{
TEST_SUIT_BEGIN;
test_unit();
TEST_UNIT_BEGIN("equal");
TEST_CHECK(xpod_common::equal(10.0, 10-0.2, 0.1), "10.0, 10-0.2, 0.1");
TEST_CHECK(xpod_common::equal(10.0, 10-0.1, 0.1), "10.0, 10-0.1, 0.1");
TEST_CHECK(xpod_common::equal(10.0, 10-0.0, 0.1), "10.0, 10-0.0, 0.1");
TEST_CHECK(xpod_common::equal(10.0, 10+0.1, 0.1), "10.0, 10+0.1, 0.1");
TEST_CHECK(xpod_common::equal(10.0, 10+0.2, 0.1), "10.0, 10+0.2, 0.1");
TEST_UNIT_END;
TEST_UNIT_BEGIN("equal");
for (auto i=0; i<20; i++) {
char buffer[128] = {0};
sprintf(buffer, "10.0, 9+0.1*%d, 0.1", i);
TEST_CHECK(xpod_common::equal(10.0, 9+0.1*i, 0.1), buffer);
}
TEST_UNIT_END;
TEST_SUIT_END;
return 0;
}
使用非常简单,当然功能也很简单,无法和cpptest,gtest这样的大型测试框架相媲美,但用到我的算法测试项目中正合适。后续有时间将增加UNIX版本的打印接口,如果能有边界测试宏那就更好了,测试算法输入接口不用再写for语句了。
最后来看看其中的一个测试效果: