我的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()<<"\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() != 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()<<"\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", 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

我的C++自动化测试程序

时间: 2024-11-09 09:44:24

我的C++自动化测试程序的相关文章

学习实践:使用模式,原则实现一个C++自动化测试程序

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

自动化测试程序之二模拟触摸屏点击事件和滑动事件(C语言)

一.测试程序编写说明 终端设备上运行的是LINUX+QT应用程序,使用触摸屏进行人机交互.经过测试人员长时间的人机交互测试,来确认系统的功能是否满足需求后.现在需要编写一个自动化的测试程序模拟触摸屏点击事件和滑动,并能够按照测试人员预设的脚本执行,比如在屏幕的某个位置需要点击某个按钮,或是屏幕或是列表需要滑动,并且这一个或几个动作需要连续执行10000次或更多.通过这样的自动测试,可以减轻测试人员的负担,还可以查看画面按钮触发N次后,画面执行N次后的系统的稳定性,如内存使用率(内存是否泄漏),c

自动化测试程序之一自定义键盘的模拟测试程序(C语言)

一.测试程序编写说明 我们做的终端设备上运行的是QT应用程序,使用自定义的键盘接口.经过测试人员长时间的人机交互测试,来确认系统的功能是否满足需求.现在需要编写一个自动化的测试程序,能够按照预设的脚本执行,比如某个按键需要连续执行10000次,或是通过连续几个按键动作执行特定的业务流程10W次.通过这样的自动测试,可以减轻测试人员的负担,还可以查看触发N次按键后,画面执行N次后的系统的稳定性,如内存使用率,cup使用率等等. 设备有4*4的键盘,包括0-9,C(Call),A,U(up),D(D

《软件测试自动化之道》- 基于反射的UI自动化测试框架 - UI Automation Test Framework

测试自动化程序的任务 基于反射的ui测试自动化程序,要完成的6项任务: 通过某种方式从测试套件程序中运行待测程序(AUT: Applicaton Under Test),以便于两个程序之间进行通信 操纵应用程序的窗体,从而模拟用户对窗体所实施的moving和resizing操作 检查应用程序窗体,确定应用程序的状态是否准确 操纵应用程序控件的属性,从而模拟用户的一些操作,比如模拟在一个TextBox控件里输入字符 检查应用程序控件的属性,确定应用程序的状态是否准确 调用应用程序的方法,从而模拟一

《软件测试自动化之道》读书笔记 之 底层的Web UI 测试

<软件测试自动化之道>读书笔记 之 底层的Web UI 测试 2014-09-28 测试自动化程序的任务待测程序测试程序  启动IE并连接到这个实例  如何判断待测web程序完全加载到浏览器  操纵并检查IE Shell  操作待测Web页面上的HTML元素的值  验证Web页面上HTML元素  示例代码 测试自动化程序的任务 底层技术的核心是,通过直接调用mshtml.dll和shdocvw.dll库来访问并且操纵IE客户区域的HTML对象. 待测程序 新建一个网站“WebAUT”,删除原来

《软件测试自动化之道》读书笔记 之 基于反射的UI测试

<软件测试自动化之道>读书笔记 之 基于反射的UI测试 2014-09-24 测试自动化程序的任务待测程序测试程序  启动待测程序  设置窗体的属性  获取窗体的属性  设置控件的属性  获取控件的属性  方法调用  待测程序代码 测试自动化程序的任务 返回 基于反射的ui测试自动化程序,要完成的6项任务: 通过某种方式从测试套件程序中运行待测程序(AUT: Applicaton Under Test),以便于两个程序之间进行通信 操纵应用程序的窗体,从而模拟用户对窗体所实施的moving和r

使用 tablib 来自动化管理测试用例,其他的工具都不用学了

你在学习 python 自动化测试吗?听过 requests 库吗?tablib 是 requests 库作者常年维护的一个可以操作 Excel 等多种文件格式,将他们变成一种通用数据集的第三方库. tablib 支持的主要数据格式有: xls, 老版 office 的 excel 文件格式: xlsx系列,新版 office 文件格式: json yaml html csv df,pandas 的 DataFrame, 需要安装 pandas 也就是说,tablib 能把不同格式的数据转化成一

python爬取凤凰网站的新闻,及其链接地址,来源,时间和内容,用selenium自动化和requests处理数据

1 import requests 2 from selenium import webdriver 3 import time 4 5 def grasp(urlT): 6 driver = webdriver.Chrome(r'C:\Program Files (x86)\Google\Chrome\Application\chromedriver.exe') #自动化测试程序工具本地所在地 7 resAll = [] #用于存储单条数据 8 rest = {} #用于存储单个数据 9 re

测试之路3——对比XML文件2

距离上一篇对比xml文件隔了很久,并不代表一直做了那么久. 其实上一次对比xml文件一直出错,其实我忽略了一个很简单的问题:我从根目录下得到的所有孩子,是这个根下的,而xml文件的组织形式如下,孩子也有可能是其他的根: <streams> <stream id = "979" presetid = "-1"> <h264> <profile>High</profile> <par_y>-1<