一个不足百行的单元测试框架:LazyTest

当实现一个算法或者写一个工具类的时候,我们总是需要写一些测试代码,但如果全部写在main函数里,难免组织混乱,不易清查;如果选用cppunit或者gtest等强大的单元测试框架,又是杀鸡用牛刀 - 太重了,不方便。另外一个可选的是TUT, Template Unit Test Framework,与前两者不同是其采用C++模板函数实现,而不是宏,虽说号称短小精悍,拿来一试也觉得颇显富态。

其实我只需要一个很简单的框架,只是针对一个算法实现,或者一个工具类写测试,而不是项目级别的。比如我写了一个max函数求两个数中较大的那个,那么测试代码可以这么写:

TESTCASE(test_max_int)
{
    ASSERT_TRUE(max(1, 10) == 10);
    ASSERT_TRUE(max(100, 10) == 100);
    ASSERT_TRUE(max(10, 10) == 10);

     return true;
}

TESTCASE(test_max_float)
{
    ASSERT_TRUE(max(1.1, 10.1) == 10.1);
    ASSERT_TRUE(max(100.1, 10.1) == 100.1);
    ASSERT_TRUE(max(10.1, 10.1) == 10.1);

     return true;
}

 

然后RUN_ALL_CASES就可以了。

仔细想了一下,这个也不难实现,主要考虑这么几个方面:

  • test case的自动注册
    这个可以在声明TESTCASE时用一个全局静态变量的构造函数实现
  • test case的管理与运行
    只要将所有的case注册到一个容器中,最后遍历该容器调用case即可
  • 宣告case失败并提高错误信息
    用一个宏来检查某个表达式,若失败则做两件事:一是output错误行与表达式;二是返回false宣告case失败.
下面就是实现,你也可以下载该文件:http://code.google.com/p/baiyanhuang/source/browse/trunk/LazyLib/LazyTest.h
//
// Description:
// A simple unit-test framework which aims to testing simple programs like utility class, algorithm...
// 
// How to use:
// You only need to know 3 macros to use this framework: TESTCASE, ASSERT_TRUE, RUN_ALL_CASES
// TESTCASE(testname)
// {
//     ASSERT_TRUE(1 + 1 ==  2);
//     return true;
// }
// ...
// RUN_ALL_CASES();
//
// Author: lzprgmr
// Date: 1/8/2011
//

#pragma once

#include <map>
#include <iostream>

#if defined(_WIN32)
#include <Windows.h>
#endif

#if !defined(LazyTestOut)
#define LazyTestOut std::cout
#endif

// typedefs
typedef unsigned int uint32_t;
typedef bool (*TestFunc) ();
typedef std::map<char*, TestFunc> TestCaseMap;

// Manage and run all test cases
class TestMgr
{
public:
	static TestMgr* Get()
	{
		static TestMgr _instance;
		return &_instance;
	}

	void AddTest(char* tcName, TestFunc tcFunc)
	{
		m_tcList[tcName] = tcFunc;
	}
	
	uint32_t RunAllCases()
	{
		uint32_t failure = 0;
		for(TestCaseMap::iterator it = m_tcList.begin(); it != m_tcList.end(); ++it)
		{
			LazyTestOut << "Running " << it->first << "... " << std::endl;
			bool bRes = RunCase(it->second);
			if(bRes) LazyTestOut << "\tPass" << std::endl;
            else failure++;
		}
		LazyTestOut << "\n" << "Totally "<< failure << " cases failed!!!" << std::endl;
		return failure;
	}

private:
	bool RunCase(TestFunc tf)
	{
		bool bRes = false;
#if defined(_WIN32)
        // Windows use SEH to handle machine exceptions
		__try
		{
			bRes = tf();
		}
		__except(EXCEPTION_EXECUTE_HANDLER)
		{
			LazyTestOut << "\tException caught!" << std::endl;
			bRes = false;
		}
#else 
        //Non-Windows OS that doesn't support SEH - the singal mechanism (SIGSEGV) can't work well as SEH to handle the problem
        bRes = tf();
#endif

		return bRes;
	}

private:
	TestCaseMap m_tcList;
};

// Register a test case
class TestCaseRegister
{
public:
	TestCaseRegister(char* tcName, TestFunc tcFunc) { TestMgr::Get()->AddTest(tcName, tcFunc); }
};


// To use this test framework, you only need to know 3 macros:
#define TESTCASE(tc)                                                                    \
	bool tc();                                                                          \
	TestCaseRegister register_##tc(#tc, tc);                                            \
	bool tc()

#define ASSERT_TRUE(expr) do {if(!(expr)) {                                             \
    LazyTestOut << "\tFailed at: " << __FILE__ << ": Line " <<__LINE__ << std::endl;    \
    LazyTestOut << "\tExpression: " << #expr << std::endl;                              \
    return false;}} while(false)

#define RUN_ALL_CASES()  do {TestMgr::Get()->RunAllCases(); } while(false)

 

如果我运行以下代码:

#include "../LazyLib/LazyTest.h"

TESTCASE(test1)
{
    ASSERT_TRUE(1 + 1 ==  2);
}

TESTCASE(test2)
{
    ASSERT_TRUE(1 + 1 !=  2);
}

TESTCASE(test3)
{
#if defined(_WIN32)
    int* p = NULL;
    *p = 10;
#endif
    ASSERT_TRUE(1 + 1 >  2);
}

int main()
{
    RUN_ALL_CASES();
    return 0;
}

 

输出结果如下:

Running test1...
        Pass
Running test2...
        Failed at: c:\source\baiyanhuang\algorithm\test.cpp: Line 12
        Expression: 1 + 1 != 2
Running test3...
        Exception caught!
Totally 2 cases failed!!!

 

这里需要注意的几点是:

  • 该代码可以在mac和windows下运行,linux下没试过,应该也可以。但是只有在Windows下用SEH对内存访问错误等硬件错误进行了处理,Mac下singal机制对SIGSEGV的处理不能像SEH那样很好的解决这个问题。
  • 写case的时候,case名字不能重复(废话?),并且必须在每个case最后返回true - 这个可能可以简化一下,还没想到怎么做~~~
  • 信息默认输出到std::out,你也可以在include该文件之前先define自己的LazyTestOut

更新:

对于每个case必须在最后显示的返回true的问题,这里可以用一个静态类来解决,主要是用一个静态成员保持状态,并由一个有返回值的函数转调我们编写的case,需要修改两个宏定义:

 

// To use this test framework, you only need to know 3 macros:
#define TESTCASE(tc)                                                                    \
	class class_##tc								\
	{										\
	public:										\
		static bool tc()							\
		{									\
			_result = true;							\
			run();								\
			return _result;							\
		}									\
		static void run();							\
	private:									\
		static bool _result;							\
	};										\
	bool class_##tc::_result = true; 						\
	TestCaseRegister register_##tc(#tc, class_##tc::tc);				\
	void class_##tc::run()	

#define ASSERT_TRUE(expr) do {if(!(expr)) {                                             \
    LazyTestOut << "\tFailed at: " << __FILE__ << ": Line " <<__LINE__ << std::endl;    \
    LazyTestOut << "\tExpression: " << #expr << std::endl;                              \
    _result = false; return;}} while(false)

 

 

posted @ 2011-01-08 10:46  lzprgmr  阅读(2833)  评论(2编辑  收藏  举报

黄将军