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

    本文转自我的另一个博客“逆水行船”

  • 相关阅读:
    Codeforces 1265A Beautiful String
    1039 Course List for Student (25)
    1038 Recover the Smallest Number (30)
    1037 Magic Coupon (25)
    1024 Palindromic Number (25)
    1051 Pop Sequence (25)
    1019 General Palindromic Number (20)
    1031 Hello World for U (20)
    1012 The Best Rank (25)
    1011 World Cup Betting (20)
  • 原文地址:https://www.cnblogs.com/Rong-/p/3928595.html
Copyright © 2011-2022 走看看