zoukankan      html  css  js  c++  java
  • 学习实践:使用模式,原则实现一个C++自动化测试程序

    个人编程中比较喜欢重构,重构能够提高自己的代码质量,使代码阅读起来也更清晰。但是重构有一个问题,就是如何保证重构后带代码实现的功能与重构前的一致,如果每次重构完成后,对此不闻不问,则会有极大的风险,如果每次重构后,都进行一边测试,则工作量会很巨大,最终可能是即使代码有重构的欲望,也会尽量克制住,不去重构。除非代码能够进行自动化测试。实际上进行测试的是接口,而不是所有代码,只要能够保持接口不变,自动化测试的工作量也没有想象中的巨大。其实我们在单元测试的时候,会测试各种异常情况,只不过,没有将这些测试写成测试代码罢了。

    在Java中有JUnit,在C#中有NUnit,在C++中,笔者并不知道有哪些自动化测试工具(笔者的孤陋寡闻)。于是就产生了自己写一个自动化测试程序的想法。
    自动化测试程序本质上应该是一个Command模式的应用案例,将多个Command对象保存起来,这些Command对象中存储着测试函数,在需要的时候,运行这些Command对象,并根据这些Command对象的执行结果判断测试是否通过。
    首先就是定义Command对象。Command对象比较简单,定义如下:

    typedef std::function<bool(TestInfo&)> TestFun;

    这是一个返回值为布尔类型,输入参数为TestInfo引用的函数对象,如果返回值返回true表示测试通过,返回false表示测试未通过。
    TestInfo也不复杂,它主要包含本次测试的一些信息,包括测试属性和测试结果等。测试属性有这个测试的名称,一些描述以及是否期望抛出异常等,测试结果就是测试是否成功。TestInfo代码如下:
    /**
    * @brief 测试信息对象,保存测试信息及测试结果。
    *
    */
    class TestInfo
    {
    public:
        TestInfo()
        {
            level = 1;
            name = "";
            subName = "";
            isOK = false;
            isWantException = false;
            remark = "";
        }
    public:
        int            level;                /**< 测试用例级别 */
        std::string    name;                /**< 测试接口名称 */
        std::string    subName;            /**< 测试接口名称具体描述 */
        bool        isOK;                /**< 测试结果 */
        bool        isWantException;    /**< 是否期望异常发生 */
        std::string remark;                /**< 备注信息 */
    };

    有了Command对象,还需要有存储及运行Command对象的地方,因此我添加了一个叫TestBaseEX的类,之所以叫Ex,是因为我以前实现过一个TestBase的类,后来在TestBase的基础上做了改进,名称编程了TestBaseEx。
    在TestBaseEX中,将Command对象存储在一个vector中,并提供了一个OnTest方法来运行这些Command对象。
    /**
    * @brief 测试基础类。
    *
    */
    class TestBaseEX
    {
    public:
        typedef std::function<bool(TestInfo&)> TestFun;
        /**
        * @brief 执行测试。
        *    @param[in] testShow    测试结果展示函数
        *
        */
        void OnTest(std::function<void(TestInfo&)> testShow)
        {
            for (auto it = m_Tests.begin(); it != m_Tests.end();
                ++it)
            {
                TestInfo info;            
                try
                {
                    bool result = (*it)(info);
                    if (info.isWantException)
                    {
                        info.isOK = false;
                    }
                    else
                    {
                        info.isOK = result;
                    }
                }
                catch (...)
                {
                    info.exception = "有异常";
                    if (info.isWantException)
                    {
                        info.isOK = true;
                    }
                }
                testShow(info);
            }
        }
    public:
        std::vector<TestFun> m_Tests;
    };

    TestBaseEX中主要是一个OnTest方法,该方法逻辑比较简单:
    循环遍历Command对象并执行,如果期望抛出异常而没有抛异常,则测试部通过,否则根据返回值判断  是否测试通过。
    传入的testShow函数对象负责对Command对象测试结果进行处理(一般是进行展示,如果通过显示为绿色,不通过,显示为红色)。 
    在创建Command对象的时候,信息越全越好,最好能将函数,参数什么的都包含进行,所以添加了一个宏,来创建Command对象。
    /**
    * @brief 添加测试对象。
    *
    */
    #define TEST_INIT(info, sub) {
        ostringstream oss;
        oss<<"position:"<<__FILE__<<"-"<<__LINE__<<"-"<<__FUNCTION__<<endl;
        info.name = __FUNCTION__;/*oss.str();*/
    }
        info.subName = sub;
        info.remark = "";
        info.isOK = true;
    #define TESTFUN_INIT(name) m_Tests.push_back(std::bind(&name, this, std::tr1::placeholders::_1))

    真正的测试类会继承自TestBaseEX,例如HiDB的测试类:

    /**
    * @brief 数据库操作测试类。
    *
    */
    class  HisDBTest: public TestBaseEX
    {
    public:
        HisDBTest();
        ~HisDBTest();
    private:
        /**
        * @brief 执行Open接口测试(连接字符串正确)。
        * @param[in] info    测试数据对象
        * @retval true:成功,false;失败
        */
        bool OnOpen(TestInfo& info);
        bool DropTable(TestInfo&);
        bool CreateTable(TestInfo&);
        bool AddRecorder(TestInfo&);
        bool AddRecorder2(TestInfo&);
        bool Scalar(TestInfo&);
        bool Scalar2(TestInfo&);
        bool Scalar3(TestInfo&);
        bool ReadRecorders(TestInfo&);
        bool ReadRecorders2(TestInfo&);
        bool ReadRecorders3(TestInfo&);
        bool DeleteRecorder(TestInfo&);
        bool OnClose(TestInfo&);
        bool OnClose_Repeat(TestInfo&);
        bool OnConnRelace2(TestInfo&);
        bool OnConnRelace3(TestInfo&);
    private:
        HiDB*    m_DB;
    };

    实现(此处和我上一篇的HiDB对应不起来,这是我很久以前的HiDB版本,要比现在的复杂很多):

    using namespace std;
    HisDBTest::HisDBTest()
    {
        TESTFUN_INIT(HisDBTest::OnOpen);
        TESTFUN_INIT(HisDBTest::DropTable);
        TESTFUN_INIT(HisDBTest::CreateTable);
        TESTFUN_INIT(HisDBTest::AddRecorder);
        TESTFUN_INIT(HisDBTest::AddRecorder2);
        TESTFUN_INIT(HisDBTest::Scalar);
        TESTFUN_INIT(HisDBTest::Scalar2);
        TESTFUN_INIT(HisDBTest::Scalar3);
        TESTFUN_INIT(HisDBTest::ReadRecorders);
        TESTFUN_INIT(HisDBTest::ReadRecorders2);
        TESTFUN_INIT(HisDBTest::ReadRecorders3);
        TESTFUN_INIT(HisDBTest::DeleteRecorder);
        TESTFUN_INIT(HisDBTest::OnConnRelace2);
        TESTFUN_INIT(HisDBTest::OnConnRelace3);
        this->m_DB = new HiDB(HiDBType_MySQL, false);
        
    }
    HisDBTest::~HisDBTest()
    {
        if (this->m_DB)
        {
            this->m_DB->Close();
            delete this->m_DB;
            this->m_DB = NULL;
        }
    }
    bool HisDBTest::OnOpen(TestInfo& info)
    {
        TEST_INIT(info, "打开数据库");
        info.remark = "(请提供数据库:host=127.0.0.1;port=3306;"
            "dbname=test;user=root;pwd=root;charset=gbk;";
        return this->m_DB->Open(
            "host=127.0.0.1;port=3306;dbname=test;user=root;pwd=root;charset=gbk;"
            );
    }
    bool HisDBTest::DropTable(TestInfo& info)
    {
        TEST_INIT(info, "ExecuteNoQuery 无参");
        return this->m_DB->ExecuteNoQuery("drop table if exists table1;");
    }
    bool HisDBTest::CreateTable(TestInfo& info)
    {
        TEST_INIT(info, "ExecuteNoQuery 无参");
        return this->m_DB->ExecuteNoQuery(
            "create table table1(column1 varchar(6) not null,"
            "column2  varchar(40) not null,"
            "column3  int not null default 1,"
            "column4 int, "
            "column5 timestamp not null default CURRENT_TIMESTAMP,"
            "column6 varchar(512),primary key (column1));");
    }
    bool HisDBTest::AddRecorder(TestInfo& info)
    {
        TEST_INIT(info, "ExecuteNoQuery C语言方式(printf)");
        return this->m_DB->ExecuteNoQuery(
            "INSERT INTO table1(Column1,Column2,Column3,Column4,Column6) "
            "VALUES('%s', '%s', %d, NULL, '%s')",
            "mytest", "my second test recorder",
            80, "this test create by xuminrong");
    }
    bool HisDBTest::AddRecorder2(TestInfo& info)
    {
        TEST_INIT(info, "Create方法,自动组成SQL语句");
        vector<HiDBParamer> paramers;
        HiDBParamer paramer1("column1", "hitest");
        paramers.push_back(paramer1);
        HiDBParamer paramer2("column2", "this is a test by xmr");
        paramers.push_back(paramer2);
        HiDBParamer paramer3(HiDBDataType_Short, "column3", "59");
        paramers.push_back(paramer3);
        HiDBParamer paramer4(HiDBDataType_Short, "column4", "59");
        paramer4.m_IsNull = true;
        paramers.push_back(paramer4);
        HiDBParamer paramer6("column6", "this is a test");// = {0};
        paramers.push_back(paramer6);
        return this->m_DB->Create("table1", paramers);
    }
    bool HisDBTest::Scalar(TestInfo& info)
    {
        TEST_INIT(info, "ExecuteScalar 使用参数数组,以HiDBRetVal作为返回值");
        vector<HiDBParamer> paramers;
        HiDBParamer paramer1("column1", "hitest");
        paramers.push_back(paramer1);
        HiDBParamer paramer2(HiDBDataType_Short, "column3", "59");
        paramers.push_back(paramer2);
        HiDBRetVal val;
        try
        {
            if (!this->m_DB->ExecuteScalar("SELECT column6 FROM table1 WHERE ? AND ?",paramers, &val))
            {
                return false;
            }
        }
        catch (HiDBException& e)
        {
            info.remark = "产生HiDBException异常:" + e.m_descript + e.m_position;
        }
        if (::strcmp(val.m_Value.c_str(), "this is a test") != 0)
        {
            return false;
        }
        return true;
    }
    bool HisDBTest::Scalar2(TestInfo& info)
    {
        TEST_INIT(info, "ExecuteScalar C语言方式(printf),以string作为返回值");
        string val;
        try
        {
            return this->m_DB->ExecuteScalar(
                "SELECT column4 FROM table1 WHERE column1='%s' AND column3=%d",
                &val, "hitest", 59);
        }
        catch (HiDBException& e)
        {
            info.remark = "产生HiDBException异常:" + e.m_descript + e.m_position;
            return false;
        }
    }
    bool HisDBTest::Scalar3(TestInfo& info)
    {
        TEST_INIT(info, "ExecuteScalar C语言方式(printf),返回空值");
        HiDBRetVal val;
        try
        {
            if (!this->m_DB->ExecuteScalar(
                "SELECT column4 FROM table1 WHERE column1='%s' AND column3=%d",
                &val, "hitest", 59))
            {
                return false;
            }
        }
        catch (HiDBException& e)
        {
            info.remark = "产生HiDBException异常:" + e.m_descript + e.m_position;
            return false;
        }
        if (val.m_IsNull)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    bool HisDBTest::ReadRecorders(TestInfo& info)
    {
        TEST_INIT(info, "ExecuteQuery 使用参数数组");
        vector<HiDBParamer> paramers;
        HiDBParamer paramer1("column1", "hitest");
        paramers.push_back(paramer1);
        HiDBParamer paramer2( "column1", "mytest");
        paramers.push_back(paramer2);
        std::shared_ptr<HiDBTable> table = this->m_DB->ExecuteQuery(
            "SELECT column1,column2 FROM table1 WHERE ? OR ? ORDER BY column1", paramers);
        if (table == NULL)
        {
            return false;
        }
        if (table->size() != 2)
        {
            return false;
        }
        
        ostringstream oss;
        for (auto it = table->begin(); it != table->end(); ++it)
        {
            for(auto item = (*it).begin(); item != (*it).end(); ++item)
            {
                //oss<<"field:"<<item->second.m_Field.c_str()<<",value:"<<item->second.m_Value.c_str()<<"
    ";
                oss<<item->second.ToSrting()<<"
    ";
            }
        }
        info.remark.append(oss.str().c_str());
        return true;
    }
    bool HisDBTest::ReadRecorders2(TestInfo& info)
    {
        TEST_INIT(info, "ExecuteQuery C语言方式(printf)");
        std::shared_ptr<HiDBTable> table = this->m_DB->ExecuteQuery(
            "SELECT column1,column2 FROM table1 WHERE column1='%s' OR column1='%s'",        
            "hitest", "mytest");
        if (table == NULL)
        {
            return false;
        }
        if (table->size() != 2)
        {
            return true;
        }
        ostringstream oss;
        for (auto it = table->begin(); it != table->end(); ++it)
        {
            for(auto item = (*it).begin(); item != (*it).end(); ++item)
            {
                //oss<<"field:"<<item->second.m_Field.c_str()<<",value:"<<item->second.m_Value.c_str()<<"
    ";
                oss<<item->second.ToSrting()<<"
    ";
            }
        }
        info.remark.append(oss.str().c_str());
        return true;
    }
    bool HisDBTest::ReadRecorders3(TestInfo& info)
    {
        TEST_INIT(info, "生成SQL语句测试");
        vector<HiDBParamer> list;
        HiDBParamer parm1("nCameraNo", 12345);
        list.push_back(parm1);
        HiDBParamer parm2(HiDBDataType_Time, "dtTime", "2012-08-06 16:44:32");
        parm2.m_Oper = HiOper_GreaterEqual;
        list.push_back(parm2);
        HiDBParamer parm3(HiDBDataType_Time, "dtTime", "2012-08-07 16:44:32");
        parm3.m_Oper = HiOper_LessEqual;
        list.push_back(parm3);
        
        HiDBParamer parm4("nEPType", 3);
        list.push_back(parm4);
        HiDBParamer parm6( "nFoward", 5);
        list.push_back(parm6);
        ostringstream oss;
        oss<<"SELECT nCameraNo,nCarNoType,cCarNo,dtTime,cAddress,"
            "cRouteChannel,nFoward,nEPType,nCaptureType,cAction,"
            "nTotalTime,nColor  FROM Illegals";
        int count = (int)list.size();
        if (count > 0)
        {
            oss<< " WHERE ";
        }
        for (int i = 0; i < count; i++)
        {
            oss<<" ? ";
            if (i != count - 1)
            {
                oss<<" AND ";
            }
        }
        oss<<" ORDER BY nNo LIMIT "<<3<<" "<<50<<endl;
        try
        {
            string sql = oss.str();
            std::shared_ptr<HiDBTable> table = this->m_DB->ExecuteQuery(sql.c_str(),list);
            if (table == NULL)
            {
                return true;
            }
            return false;
        }
        catch (HiDBException& ex)
        {
            info.remark = ex.m_sql;
            return true;
            
        }
        catch (...)
        {
            return false;
        }
    }
    bool HisDBTest::DeleteRecorder(TestInfo& info)
    {
        TEST_INIT(info, "Delete 使用参数数组");
        vector<HiDBParamer> paramers;
        HiDBParamer paramer1("column1", "hitest");
        paramers.push_back(paramer1);
        HiDBParamer paramer2( HiDBDataType_Short, "column3", "59");
        paramers.push_back(paramer2);
        return this->m_DB->Delete("table1", paramers);
    }
    static string  getConn(string ip, TestInfo& info);
    bool HisDBTest::OnConnRelace2(TestInfo& info)
    {
        TEST_INIT(info, "替换数据库IP");
        return getConn("127.0.15.43", info)=="host=127.0.0.1;port=3306;"
            "dbname=test;user=root;pwd=root;charset=gbk;";
    }
    bool HisDBTest::OnConnRelace3(TestInfo& info)
    {
        TEST_INIT(info, "替换数据库IP");
        return getConn("127.1.5.1", info)=="host=127.1.5.1;port=3306;"
            "dbname=test;user=root;pwd=root;charset=gbk;";
    }
    static string  getConn(string m_CenterIP, TestInfo& info)
    {
        string conn = "host=127.0.0.1;port=3306;"
            "dbname=test;user=root;pwd=root;charset=gbk;";
        if (!m_CenterIP.empty())
        {
            string::size_type pos1 = conn.find_first_of('=');
            string::size_type pos2 = conn.find_first_of(';', pos1);
            //取数据库IP
            string ip = conn.substr(pos1 + 1, pos2 - pos1 - 1);
            string::size_type pos_connect = ip.find_first_of('.', ip.find_first_of('.') + 1);
            string::size_type pos_center = m_CenterIP.find_first_of('.', 
                m_CenterIP.find_first_of('.') + 1);
            //比较IP前两段是否一样
            if (ip.substr(0, pos_connect) != m_CenterIP.substr(0, pos_center))
            {
                conn.replace(pos1 + 1, ip.size(), m_CenterIP);
            }
        }
        return conn;
    }

    源代码下载地址:http://download.csdn.net/detail/xumingxsh/7791923

    本文转子我的csdn博客。

  • 相关阅读:
    实战:上亿数据如何秒查(转)
    jquery json 操作(转)
    企业模式之Unit Of Work模式
    判断一个网站用什么服务器
    js面向对象的封装方法,【案例】
    直线拟合算法
    互联网公司年终奖哪家强?都是土豪啊
    wifidog用php实现验证流程
    想学android进来看看吧~ ~
    Android自己定义视图(一):带下划线的TextView
  • 原文地址:https://www.cnblogs.com/admin11/p/3926734.html
Copyright © 2011-2022 走看看