• 一个不足百行的单元测试框架: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)
    
    
  • 相关阅读:
    IDEA 使用 Gradle 创建 Java 项目
    Java HttpsUnits 工具类实现 Https
    Android Thread 常用方法
    Android Handler、Message 常用方法
    Android 透明导航键遮挡布局
    Android 通过 JDBC 连接远程数据库
    python 连接数据库
    备份及更新 Portainer
    secureCRT免密码登陆Linux
    Linux vi 编辑器常见命令的使用
  • 原文地址:https://www.cnblogs.com/baiyanhuang/p/1930510.html
Copyright © 2020-2023  润新知