zoukankan      html  css  js  c++  java
  • 一个不足百行的单元测试框架: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)
    
    
  • 相关阅读:
    格式化数字,将字符串格式的数字,如:1000000 改为 1 000 000 这种展示方式
    jquery图片裁剪插件
    前端开发采坑之安卓和ios的兼容问题
    页面消息提示,上下滚动
    可以使用css的方式让input不能输入文字吗?
    智慧农村“三网合一”云平台测绘 大数据 农业 信息平台 应急
    三维虚拟城市平台测绘 大数据 规划 三维 信息平台 智慧城市
    农业大数据“一张图”平台测绘 大数据 房产 国土 农业 信息平台
    应急管理管理局安全生产预警平台应急管理系统不动产登记 测绘 大数据 规划 科教 三维 信息平台
    地下综合管廊管理平台测绘 大数据 地下管线 三维 信息平台
  • 原文地址:https://www.cnblogs.com/baiyanhuang/p/1930510.html
Copyright © 2011-2022 走看看