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

在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 = ;
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",
, "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", "");
paramers.push_back(paramer3);
HiDBParamer paramer4(HiDBDataType_Short, "column4", "");
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", "");
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") != )
{
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", );
}
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", ))
{
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() != )
{
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()<<"\r\n";
oss<<item->second.ToSrting()<<"\n";
}
}
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() != )
{
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()<<"\r\n";
oss<<item->second.ToSrting()<<"\n";
}
}
info.remark.append(oss.str().c_str());
return true;
}
bool HisDBTest::ReadRecorders3(TestInfo& info)
{
TEST_INIT(info, "生成SQL语句测试");
vector<HiDBParamer> list;
HiDBParamer parm1("nCameraNo", );
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", );
list.push_back(parm4);
HiDBParamer parm6( "nFoward", );
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 > )
{
oss<< " WHERE ";
}
for (int i = ; i < count; i++)
{
oss<<" ? ";
if (i != count - )
{
oss<<" AND ";
}
}
oss<<" ORDER BY nNo LIMIT "<<<<" "<<<<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", "");
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 + , pos2 - pos1 - );
string::size_type pos_connect = ip.find_first_of('.', ip.find_first_of('.') + );
string::size_type pos_center = m_CenterIP.find_first_of('.',
m_CenterIP.find_first_of('.') + );
//比较IP前两段是否一样
if (ip.substr(, pos_connect) != m_CenterIP.substr(, pos_center))
{
conn.replace(pos1 + , ip.size(), m_CenterIP);
}
}
return conn;
}

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

本文转子我的csdn博客。

学习实践:使用模式,原则实现一个C++自动化测试程序的更多相关文章

  1. 前端学习实践笔记--JavaScript深入【1】

    这一年中零零散散看过几本javascript的书,回过头看之前写过的javascript学习笔记,未免有点汗颜,突出“肤浅”二字,然越深入越觉得javascript的博大精深,有种只缘身在此山中的感觉 ...

  2. ReactNative学习实践--动画初探之加载动画

    学习和实践react已经有一段时间了,在经历了从最初的彷徨到解决痛点时的兴奋,再到不断实践后遭遇问题时的苦闷,确实被这一种新的思维方式和开发模式所折服,react不是万能的,在很多场景下滥用反而会适得 ...

  3. 跟着ZHONGHuan学习设计模式--桥接模式

    转载请注明出处! ! !http://blog.csdn.net/zhonghuan1992 全部配套代码均在github上:https://github.com/ZHONGHuanGit/Desig ...

  4. 设计模式学习之访问者模式(Visitor,行为型模式)(21)

    参考:https://www.cnblogs.com/edisonchou/p/7247990.html 在患者就医时,医生会根据病情开具处方单,很多医院都会存在以下这个流程:划价人员拿到处方单之后根 ...

  5. Java设计模式学习记录-模板方法模式

    前言 模板方法模式,定义一个操作中算法的骨架,而将一些步骤延迟到子类中.使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤. 模板方法模式 概念介绍 模板方法模式,其实是很好理解的,具体 ...

  6. Java设计模式学习记录-迭代器模式

    前言 这次要介绍的是迭代器模式,也是一种行为模式.我现在觉得写博客有点应付了,前阵子一天一篇,感觉这样其实有点没理解透彻就写下来了,而且写完后自己也没有多看几遍,上次在面试的时候被问到java中的I/ ...

  7. Java设计模式学习记录-解释器模式

    前言 这次介绍另一个行为模式,解释器模式,都说解释器模式用的少,其实只是我们在日常的开发中用的少,但是一些开源框架中还是能见到它的影子,例如:spring的spEL表达式在解析时就用到了解释器模式,以 ...

  8. 学习vue就是那么简单,一个简单的案例

    vue是前端兴起的一个javascript库,相信大家都使用过jQuery,虽然vue和jQuery没有可比性,但从熟悉的角度去理解新的东西或许会容易接受一些,有时候由于思想和模式的转变会带来阵痛,但 ...

  9. 《Head first设计模式》学习笔记 – 迭代器模式

    <Head first设计模式>学习笔记 – 迭代器模式 代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示. 爆炸性新闻:对象村餐厅和对象村煎饼屋合并了!真是个 ...

随机推荐

  1. JDBC中的PreparedStatement-防止SQL注入攻击

    在JDBC对数据库进行操作的时候,SQL注入是一种常见的针对数据库的注入攻击方式.如下面的代码所演示,在我们的提交字段中掺入了SQL语句,会使得程序的登录校验失效: package org.lyk.m ...

  2. CRM PrincipalObjectAccess(POA)

    PrincipalObjectAccess (POA) table is an important table which holds all grants share on CRM objects. ...

  3. jquery mobile转场时加载js失效(转)

    jquery mobile拦截了所有的http请求,并使用ajax请求取代传统的http.请求发出后,框架会将请求的内容插入到页面中data- role="page"的部分,取代原 ...

  4. 手机端的各种默认样式比如 ios的按钮变灰色

    1.ios按钮变灰色,给按钮加样式: -webkit-appearance: none; 2.有圆角话 ; } 3.去除Chrome等浏览器文本框默认发光边框 input:focus, textare ...

  5. c语言实现词频统计

    需求: 1.设计一个词频统计软件,统计给定英文文章的单词频率. 2.文章中包含的标点不计入统计. 3.将统计结果以从大到小的排序方式输出. 设计: 1.因为是跨专业0.0···并不会c++和java, ...

  6. DataRow对象的行状态(RowState)和行版本(DataRowVersion)属性

    DataRow对象有两个比较重要的属性,分别是行状态(RowState)和行版本(DataRowVersion),通过这两个属性能够有效的管理表中的行.下面简要的介绍一下行状态和行版本的特点和关系. ...

  7. IOS学习之路- 运行过程

    1. 执行Main函数(在main.m文件中) 2. 加载MainStoryborad.storyboard文件 * 创建ViewController文件 * 根据storyboard文件中描述创建V ...

  8. linux内核调试指南

    linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级调试 ***第一部分:基础知识*** 总纲:内核世界的陷阱 源码阅读的陷阱 代码调试的陷阱 原理理解的陷阱 ...

  9. jmeter随笔(1)-在csv中数据为json格式的数据不完整

    昨天同事在使用jmeter遇到问题,在csv中数据为json格式的数据,在jmeter中无法完整的取值,小怪我看了下,给出解决办法,其实很简单,我们一起看看,看完了记得分享给你的朋友. 问题现象: 1 ...

  10. JS 中数组的排序和去重

    在 PHP 中,数组有很多排序方法,不过其他语言的数组中大概是不会像 JS 的数组一样,包罗万象,啥都通吃的.所以 JS 的数组排序情况就略多一些了. 简单粗暴的排序: 赤果果的sort: var   ...