今天遇到一个棘手的问题,用robotium框架真机
测试客户端时,跑到一半会crash,搜了一堆资料终于解决了
我的程序引起crash主要原因有两个:
1.用Robotium测试框架跑多个用例(写在同一个类里),只有第一个通过,第二个会卡顿, 导致crash
原因是若同一个类里写多个测试方法,每个测试方法都会执行一次setUp() 和tearDown()方法,所以每一个用例开始时都是用的同一个启动Activity,如果你上一个用例退出时没有返回到那个Activity,执行下一个用例时会找不到启动Activity,就报错了
解决方法:
在tearDown()里加上返回到开始Activity的方法:solo.goBackto("startActivity");
2.再跑时,发现程序还是会卡在第一个用例结束时无法退出,再检查,
tearDown方法写错了,之前按照网上资料写的,并不好使,还是会引起崩溃
@Override public void tearDown() throws Exception { try { solo.finalize();// 执行清理 工作} catch (Throwable e) { e.printStackTrace(); } Activity myActivity=getActivity(); if(myActivity!=null) myActivity.finish();// 测试结束,关闭应用程序 super.tearDown(); } |
把关闭方法修改成solo.finishOpenedActivities(); OK了,至于为什么我也没有研究,有谁知道的还请留言告诉我,不胜感激~
最后,我的工程整个tearDown方法如下:
@Override public void tearDown() throws Exception { //关闭之前先回到主页面 Boolean notClosed = true; while (notClosed) { solo.goBack(); if (solo.waitForText("确定退出客户端吗", 1, 100, false, true)) { notClosed = false; solo.clickOnButton("取消"); break; } } //关闭 try { this.solo.finishOpenedActivities(); } catch (Throwable e) { e.printStackTrace(); } Activity myActivity=getActivity(); if(myActivity!=null) myActivity.finish(); super.tearDown(); } |
引言
大凡集群系统的性能、
压力测试,都要通过监控系统进行收集整理。其中ganglia是集群监控最常用的工具之一。它与Hadoop生态圈结合的非常好,且性能优良,不会对系统本身性能造成影响。
Ganglia是UC Berkeley发起的一个开源集群监视项目,包含gmond、gmetad两个服务,以及一个
web展示前端。本身部署后就立即可以对cpu、memory、network、disk等情况进行监控汇总。gmond负责收集系统的这些监控指标数据,一般若干个节点会有一个master,负责从子节点上通过tcp协议抓取这些节点的xml格式的监控数据。若干master再向更上一层的master汇总,直到gmetad这一层会将所有数据保存到rrd数据库中。这样层层抓取进而汇总的模式,保证了节点性能不至于受到gmond监控的影响,且各级master也不会产生过多的网络IO压力。
RRDTool负责保存这些监控数据到RRD(Round RobinDatabase)文件中,RRD数据库顾名思义是一个环装的
数据库。越久远的数据在这个数据库中就会被不断规整合并,点与点之间的时间差距越来越大,越来越稀疏,当时的细节也跟着逐渐丢失。这样的数据库对于存储大规模集群的监控数据是非常有好处的。它保证了RRD文件的大小可以限制在一定范围内,而且对于集群运维来说,我们往往只关心近期的数据细节,至于过去很久的监控数据看不到细节也没多大妨碍。RRDTool还可以负责绘图,ganglia的web展示上的图正是由此而来。
ganglia和RRD虽然占用系统资源极少,但当集群规模超过5K之后,gmetad对监控数据的收集越来越显得力不从心,而RRD文件即使是高效且压缩的,也同样面临机器IO能力不足的问题。这些都是ganglia这套体系的短板,对于大多数公司团体来说,集群规模远没有达到触发ganglia瓶颈的程度,但是对于云梯来说,这个天花板却是触手可及。
同时RRD数据库的远期数据细节丢失问题,对于集群运维来说毫无影响,但是对于测试人员来说却是一个灾难。因为细节的丢失将导致刚做的
性能测试无法与一个月前甚至一年前的同样的
测试用例得到的结果进行对比。如果不解决这个问题,那么集群的性能监控对于测试人员的帮助将非常有限。
破解难题
上面提到的问题,在云梯这种大规模集群的测试中都遇到了。早期云梯测试人员试图将监控数据从RRD数据库中提取出来保存到mysql中,这样方便查询、对比、展示。但是对于上百个节点的测试集群来说,mysql数据库往往只需一周时间就会被撑爆。一个简单的指标数据的查询往往就要等上半个多小时才能从mysql中提取出来。而监控数据保存到RRD文件,再使用RRDTool导出监控数据到mysql中,整个过程也是及其低效的。在我接手云梯1项目的集成测试之后,深深的被这个问题所困扰,整理测试数据让每一次的集成测试结果汇总都苦不堪言。
2012年上半年,测试这边开始主导设计DST(分布式
测试工具)测试框架,随着云梯项目的进行,DST也在不断迭代中向前发展。其中集群监控数据的收集整理展示被我标记为首要必须完成的功能。经过深思熟虑,最终采用了“替代gmetad,并直接存储监控数据至HBase”这个解决方案,设计的工具被我命名为“GGL2HBase”。
这个方案是做成一个与gmetad类似的服务,直接去所有节点的gmond master上抓取监控数据,从网络拿到后,直接从内存中存储到一个HBase集群中。利用HBase的高效解决了查询慢的问题,同时RRD文件远期数据细节丢失的问题也迎刃而解。
在前面的
玩转gtest - 断言中, 我们提到了ASSERT_*系列的断言只是在当前函数返回,并非退出当前
测试案例,因为ASSERT_*系列是通过return来实现的(因此 ASSERT_*系列不能在返回值不为void的函数内出现)。要退出当前测试案例,一个最简单的方法就是通过抛异常,然后让gtest捕获这一异常。示例如下:
void Func(int a, int b) { throw "b==0"; EXPECT_EQ(0, a\b); printf("End of Func"); } { Func(5, 0); printf("End Call Func(5, 0)"); } int _tmain(int argc, _TCHAR* argv[]) { testing::GTEST_FLAG(catch_exceptions) = 1; testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } |
(上面的两个printf函数都不会执行。)
要退出当前测试案例,你只需要两步:
1.设置catch_exception标志,在main函数或是在你的测试案例前都可以。
2.要跳出测试案例时,只需要通过throw抛出任意异常即可。
为何通过这种方法可以跳出当前测试案例,请参考玩转gtest - 深入解析gtest。
需要注意的是:假如使用的是TEST_F宏,跳出当前测试案例后,会执行TearDown(),因此不必当心TearDown中释放资源的操作不会执行。
我认为一个好的测试案例,应该是在你的测试函数中,比如TEST宏内,清晰的表达出你要测试的对象,以及预期的测试结果。因此,通常情况下,EXPECT_*和 ASSERT_*应该尽量在测试函数中出现,而不是在测试函数内调用的另外函数或是里面很多层的函数内才出现。(比如上面的Func函数中的 EXPECT_EQ)。
自动化测试工具可以减少测试
工作量,提高测试工作效率,但首先是能够选择一个合适的且满足企业信息系统工程环境的自动化测试工具,因为不同的测试工具,其面向的测试对象是不一样的。按照测试工具的主要用途和应用领域,可以将自动化测试工具分为以下几类:
LoadRunner 特点a,支持的协议多且个别协议支持的版本比较高;特点b,负载压力测试方案设置灵活;特点c,丰富的资源监控;特点d,报告可以导出到Word、Excel以及HTML格式。
QALoad (1).测试接口多;(2)可预测系统性能;(3)通过重复测试寻找瓶颈问题;(4)从控制中心管理全局负载测试;(5)可验证应用的扩展性;(6)快速 创建仿真的负载测试;(7)性能价格比较高。此外,QALoad不单单测试Web应用,还可以测试一些后台的东西,比如
SQL Server等。只要它支持的协议,都可以测试。
Benchmark Factory 首先它可以测试服务器群集的性能;其次,可以实施基准测试;最后,可以生成高级脚本。
SilkPerformance:
E-Test Suite 由Empirix公司开发的测试软件,能够和被测试应用软件无缝结合的Web应用测试工具。工具包含e-Tester、e-Load和e-Monitor,这三种工具分别对应
功能测试、压力测试以及应用监控,每一部分功能相互独立,测试过程又可彼此协同。
JMeter 是一个专门为运行和服务器负载测试而设计、100%的纯Java桌面运行程序。原先它是为Web/HTTP测试而设计的,但是它已经扩展以支持各种各样的 测试模块。它和HTTP和SQL(使用JDBC)的模块一起运行。它可以用来测试静止或活动资料库中的服务器运行情况,可以用来模拟服务器或网络系统在重 负载下的运行情况。它也提供了一个可替换的界面用来定制数据显示,测试同步及测试的创建和执行。
WAS 是Micro$oft提供的免费的Web负载压力测试工具,应用广泛。WAS可以通过一台或者多台客户机模拟大量用户的活动。WAS支持身份验证、加密和Cookies,也能够模拟各种浏览器和Modem速度,它的功能和性能可以与数万美元的产品媲美。
ACT 或称MSACT,它是
微软的Visual Studio和Visual Studio.net带的一套进行程序压力测试的工具。ACT不但可以记录程序运行的详细数据参数,用图表显示程序运行情况,而且安装和使用都比较简单,结果阅读叶很方便,是一套较理想的测试工具。
OpenSTA 它的全称是Open System Testing Architecture。OpenST的特点是可以模拟很多用户来访问需要测试的网站,它是一个功能强大、自定义设置功能完备的软件。但是,这些设置大 部分需要通过Script来完成,因此在真正使用这个软件之前,必须
学习好它的Script编写。如果需要完成很复杂的功能,Script的要求还比较 高。当然这也是它的优点,一些程序员不会在意编写Script的。
PureLoad 一个完全基于Java的测试工具,它的Script代码完全使用XML。所以,编写Script很简单。它的测试包含文字和图形并可以输出为HTML文件。由于是基于Java的软件,因此PureLoad可以通过Java Beans API来增强软件功能。
功能测试
WinRunner 企业级的功能测试工具,用于检测应用程序是否能够达到预期的功能及正常运行,自动执行重复任务并优化测试工作,从而缩短测试时间。通过自动录制、检测和回防用户的应用操作,从而提高测试效率。
QARun 一款自动回归测试工具,与Winrunner比较学习成本要低很多。不过要安装QARun必须安装.net环境,另外它还提供与TestTrack Pro的集成。
Rational Robot 我经常使用的测试工具,属于Rational TestSuite中的一员,对于Visual studio 6编写的程序支持的非常好,同时还支持Java Applet、HTML、
Oracle Forms、People Tools应用程序的支持。要支持Delphi程序的测试还必须下载插件。Rational Robot的语法使用Basic语法,它的语言使用SQABasic。
Functional Tester 它是Robot的Java实现版本,在Rational被
IBM收购后发布的。在Java的浪潮下,Robot被移植到了Eclipse平台,并完全支持 Java和.net。可以使用VB.net和Java进行脚本的编写,当然了录下脚本让后做做修改是最爽的事情了。由于支持Java,那么对测试脚本进行 测试也变成了可能。更多的信息请到IBM developerworks上查看,另外还提供试用版本下载。
白盒测试
Logiscope
PRQA
Junit
DevPartner
Rational Purify
测试管理
TestDirector MI的测试管理工具,可以与winrunner、Loadrunner、QuickTestPro进行集成。除了可以跟踪Bug外,还可以编写测试用例、管理测试进度等等,是测试管理的首选软件。
TestManager Rational Testsuite中的一员,可以用来编写测试用例、生成Datapool、生成报表、管理缺陷以及日志等等。是一个企业级的强大测试管理工具。缺点是必须和其它组件一起使用,测试成本比较高。
TrackRecord 一款擅长于Bug管理的工具,与TestDirecotr和Testmanager比较起来是很light的。不过至今还没有配成功过。:(
Bugzilla 一个产品缺陷的记录及跟踪工具,它能够为你建立一个完善的Bug跟踪体系,包括报告、查询并产生报表、处理解决等几个部分。它的主要特点为:基于Web方式,安装简单;有利于缺陷的清楚传达;系统灵活,可配置性很强;自动发送Email。
Jira 是一个Bug管理工具,自带一个Tomcat 4;同时有简单的工作流编辑,可用来定制流程;数据存储在HSQL数据引擎中,因此只要安装了JDK这个工具就可以使用。相比较Bugzilla来说有不少自身的特点,不过可惜它并不是开源工具,有Lisence限制。
测试辅助
SmartDraw 用于绘制UCML,进行负载压力测试需求分析。对压力测试测试前的工作很有帮助。
SDemo 我个人比较喜欢用这个工具,可以将操作录成EXE文件,并回放出来。这样就避免了那些偶尔才出现的Bug!
1.什么是java当中的软件包?
软件包为java类提供了命名空间
2.为什么要使用软件包?
可以使用不同的命名空间,命名相同的类名,以软件包的包名做区分。
3.如何给一个类打包?
使用包名关键字package,一个类的全名应该是“包名” + "类名"。
4. "javac -d . Test.java " 1) -d 包路径
2)"." 表示当前目录
3)执行编译命令是 java com.test.java.test才可以执行。
5."cd .."返回上一级目录
6.实例
//1.将类放置到一个包当中,需要使用package “包名” //2.编译时需要使用-d参数,该参数的作用是依照包名生成相应的文件夹。 “.”当前目录下 //3.一个类的全名应该是"包名" + "." + "类名" com.test.java.Test //4.包名的命名规范, //1)要求包名所有的字母都要小写; //2)包名一般情况下,是域名倒写,或者是公司名称倒写; package com.test.java; class Test{ public static void main(String args[]){ System.out.println("Other printlen"); } } |
7.Java当中的访问权限
1)public:公共权限 可以修饰类,成员变量和成员函数。
a) 跨包调用文件的时候,对不同包的类的引用。
b)在一个包的外面调用另外一个包里面的一个类的成员变量或者是成员方法。
2)private:私有权限 可以修饰成员变量和成员函数。
a)修饰类的成员变量或者成员方法。
b)声明的作用域只在类文件里面。
3)default:包级别权限(不写权限修饰符,就是default权限),可以修饰类,成员变量和成员函数。
作用域是声明的包范围,不能跨包调用。
4)protected:保护权限
作用域继承声明该类和子类的作用域。
8.包的导入:
使用关键字 import 包名.要导入的类名;
import com.test.java.person;
9.如果一个类被声明为public权限的话,那么该类的类名和源文件的名字就必须一致。
10.访问权限与继承
如果子类和父类不在同一个包当中,子类可以继承到父类当中的default权限的成员变量和成员函数,由于权限不够,无法使用父类的default成员变量和成员函数。
11.protected权限
1)protected和default拥有一样的功能,但是该权限只能修饰成员变量和成员函数。
2)另外protected权限允许跨包继承,子类可以使用父类当中protected的成员变量和成员函数。
12.包的访问或者是作用域权限大小顺序,从左到右由大到小。
public > protected > default > private
13.权限修饰的目的,主要是为了实现对类的封装性。
在java中一个类的类的成员变量和成员函数,要尽可能的使访问权限尽可能的小。(封装性)
1.连接池的介绍:
1.1应用背景:
一般的应用程序都会访问到
数据库,在程序访问数据库的时候,每一次数据访问请求都必须经过下面几个步骤:建立数据库连接,打开数据库,对数据库中的数据进行操作,关闭数据库连接。而建立数据库连接和打开数据库是一件很消耗资源并且费时的
工作,如果在系统中很频繁的发生这种数据库连接,必然会影响到系统的性能,甚至会导致系统的崩溃。
1.2技术思想:
在系统初始化阶段,建立一定数量的数据库连接对象(Connection),并将其存储在连接池中定义的容器中。当有数据库访问请求时,就从连接池中的这个容器中拿出一个连接;当容器中的连接已经用完,并且还没有达到系统定义的最大连接数时,可以再创建一个新的连接,当当前使用的连接数达到最大连接数时,就要等待其他访问请求将连接放回容器后才能使用。当使用完连接的时候,必须将连接放回容器中,这样不同的数据库访问请求就可以共享这些连接,通过重复使用这些已经建立的数据库连接,可以解决上节中说到的频繁建立连接的缺点,从而提高了系统的性能。
经过上述描述,我们可以归纳出数据库连接池的主要操作:
(1)首先建立一个数据库连接池对象
(2)初始化一定数量的数据库连接,放入连接池对象的容器中
(3)当有数据库访问请求时,直接从连接池的容器中得到一个连接,这里出现三种情况:
(a)当容器中的还有连接时,则返回给数据库访问请求者一个连接
(b)当容器中没有连接时,并且当前建立的连接数没有达到系统定义的最大连接数,则创建一个新的数据库连接。
(c)当容器中的没有连接并且当前建立的连接数达到系统定义的最大连接数,则当前访问数据库请求就要等待其他访问请求释放连接。
(4)当数据库访问完成后,应该将连接放回连接池的容器中。
(5)当服务停止时,需要先释放数据库连接池中的所有数据库连接,然后再释放数据库连接池对象。
2.编程实现:
头文件(connection_pool.h):
/* *File: connection_pool.h *Author: csc */ #ifndef_CONNECTION_POOL_H #define _CONNECTION_POOL_H #include<mysql_connection.h> #include<mysql_driver.h> #include<cppconn/exception.h> #include<cppconn/driver.h> #include<cppconn/connection.h> #include<cppconn/resultset.h> #include<cppconn/prepared_statement.h> #include<cppconn/statement.h> #include<pthread.h> #include<list> usingnamespace std; usingnamespace sql; classConnPool{ private: intcurSize;//当前已建立的数据库连接数量 intmaxSize;//连接池中定义的最大数据库连接数 stringusername; stringpassword; stringurl; list<Connection*>connList;//连接池的容器队列 pthread_mutex_tlock;//线程锁 staticConnPool *connPool; Driver*driver; Connection*CreateConnection();//创建一个连接 voidInitConnection(int iInitialSize);//初始化数据库连接池 voidDestoryConnection(Connection *conn);//销毁数据库连接对象 voidDestoryConnPool();//销毁数据库连接池 ConnPool(stringurl,string user,string password,int maxSize);//构造方法 public: ~ConnPool(); Connection*GetConnection();//获得数据库连接 voidReleaseConnection(Connection *conn);//将数据库连接放回到连接池的容器中 staticConnPool *GetInstance();//获取数据库连接池对象 }; #endif /*_CONNECTION_POOL_H */ |
头文件中定义了一个容器connList,里面存放了很多个未使用的连接;在对容器内的连接进行操作的时候,需要加锁来保证程序的安全性,所以头文件中定义了一个lock,通过使用lock保证了同一时间只有一个线程对容器进行操作。
连接池类要统一管理整个应用程序中的连接,所以在整个系统中只需要维护一个连接池对象,试想:如果系统中定义了多个连接池对象,那么每一个对象都可以建立maxSize个连接,这样就失去了创建连接池的初衷,破环了通过连接池统一管理系统中连接的思想。所以这里使用单例模式编写连接池类,单例模式确保一个类只有一个实例,自己进行实例化并且向整个系统提供这个实例。在头文件中,我们定义了一个静态的连接池对象connPool,连接池类提供一个静态的公共方法GetInstance(),外部程序通过调用这个方法来获得连接池对象。并且将连接池类的构造函数定义为私有的,外部的应用程序不能够通过new来实例化连接池类,只能通过GetInstance()方法获得连接池对象;在GetInstance()方法中需要判断连接池类中定义的connPool是否为NULL,若为NULL则调用私有构造函数实例化connPool,若不为空,则直接返回connPool。这样就实现了连接池类的单例模式,从而保证了系统运行过程中只建立一个连接池类的实例对象。
在实例化连接池类的对象时,要对连接池做一些初始化的操作,即建立一定数量的数据库连接。程序中通过InitConnection(intiInitialSize)方法对连接池进行初始化,创建iInitialSize个连接,并且将这些连接放在连接池中的容器connList中,每新建一个连接,curSize就加1。当有数据库访问请求时,需要从连接池中获取一个连接,通过GetConnection()方法实现:首先判断容器中是否还有连接,如果有,则拿出容器中的第一个连接,并且将该连接移出容器;获得的连接要进行判断,如果连接已经关闭,则回收该连接的内存空间,并且重新创建一个连接;然后判断新创建的连接是否为空,如果为空,则说明当前已经建立连接的数量并不是curSize个,而是(curSize-1)个(应该除去这个空连接)。如果容器中已经没有连接了,则要判断当前的curSize值是否已经达到规定的maxSize,如果没有小于maxSize,将建立一个新的连接(++curSize)。如果超过maxSize则等待其他数据库访问请求释放数据库连接。
连接使用完以后,需要将连接放回连接池中,通过ReleaseConnection(sql::Connection* conn)方法实现,它的实现非常简单,就是将传进来的connection连接添加到连接池的容器中。
当需要回收连接池的内存空间时,需要先回收连接池中所有连接的内存空间,然后再释放连接池对象的内存空间。
实现数据库连接池主要的步骤就是上述这些,具体的代码实现如下所示:
#include<stdexcept> #include<exception> #include<stdio.h> #include"connection_pool.h" usingnamespace std; usingnamespace sql; ConnPool*ConnPool::connPool=NULL; //连接池的构造函数 ConnPool::ConnPool(stringurl, string userName,string password, int maxSize) { this->maxSize=maxSize; this->curSize=0; this->username=userName; this->password=password; this->url=url; try{ this->driver=sql::mysql::get_driver_instance(); } catch(sql::SQLException&e) { perror("驱动连接出错;\n"); } catch(std::runtime_error&e) { perror("运行出错了\n"); } this->InitConnection(maxSize/2); } //获取连接池对象,单例模式 ConnPool*ConnPool::GetInstance(){ if(connPool==NULL) { connPool=newConnPool("tcp://127.0.0.1:3306","root","root",50); } returnconnPool; } |
//初始化连接池,创建最大连接数的一半连接数量
voidConnPool::InitConnection(int iInitialSize) { Connection*conn; pthread_mutex_lock(&lock); for(inti=0;i<iInitialSize;i++) { conn=this->CreateConnection(); if(conn){ connList.push_back(conn); ++(this->curSize); } else { perror("创建CONNECTION出错"); } } pthread_mutex_unlock(&lock); } //创建连接,返回一个Connection Connection*ConnPool::CreateConnection(){ Connection*conn; try{ conn=driver->connect(this->url,this->username,this->password);//建立连接 returnconn; } catch(sql::SQLException&e) { perror("创建连接出错"); returnNULL; } catch(std::runtime_error&e) { perror("运行时出错"); returnNULL; } } //在连接池中获得一个连接 Connection*ConnPool::GetConnection(){ Connection*con; pthread_mutex_lock(&lock); if(connList.size()>0)//连接池容器中还有连接 { con=connList.front();//得到第一个连接 connList.pop_front();//移除第一个连接 if(con->isClosed())//如果连接已经被关闭,删除后重新建立一个 { deletecon; con=this->CreateConnection(); } //如果连接为空,则创建连接出错 if(con==NULL) { --curSize; } pthread_mutex_unlock(&lock); returncon; } else{ if(curSize< maxSize){//还可以创建新的连接 con= this->CreateConnection(); if(con){ ++curSize; pthread_mutex_unlock(&lock); returncon; } else{ pthread_mutex_unlock(&lock); returnNULL; } } else{//建立的连接数已经达到maxSize pthread_mutex_unlock(&lock); returnNULL; } } } //回收数据库连接 voidConnPool::ReleaseConnection(sql::Connection * conn){ if(conn){ pthread_mutex_lock(&lock); connList.push_back(conn); pthread_mutex_unlock(&lock); } } //连接池的析构函数 ConnPool::~ConnPool() { this->DestoryConnPool(); } //销毁连接池,首先要先销毁连接池的中连接 voidConnPool::DestoryConnPool(){ list<Connection*>::iterator icon; pthread_mutex_lock(&lock); for(icon=connList.begin();icon!=connList.end();++icon) { this->DestoryConnection(*icon);//销毁连接池中的连接 } curSize=0; connList.clear();//清空连接池中的连接 pthread_mutex_unlock(&lock); } //销毁一个连接 voidConnPool::DestoryConnection(Connection* conn) { if(conn) { try{ conn->close(); } catch(sql::SQLException&e) { perror(e.what()); } catch(std::exception&e) { perror(e.what()); } deleteconn; } } |
/* * main.cpp * * Created on: 2013-3-26 * Author: holy */ #include "connection_pool.h" namespace ConnectMySQL { //初始化连接池 ConnPool *connpool = ConnPool::GetInstance(); void run() { Connection *con; Statement *state; ResultSet *result; // 从连接池中获取mysql连接 con = connpool->GetConnection(); state = con->createStatement(); state->execute("use holy"); // 查询 result = state->executeQuery("select * from student where id < 1002"); // 输出查询 while (result->next()) { int id = result->getInt("id"); string name = result->getString("name"); cout << id << " : " << name << endl; } delete state; connpool->ReleaseConnection(con); } } int main(int argc, char* argv[]) { ConnectMySQL::run(); return 0; } |
使用gtest也有很长一段时间了,这期间也积累了一些经验,所以分享一下。GTest为我们提供了便捷的
测试框架,让我们只需要关注案例本身。如何在GTest框架下写出优美的
测试案例,我觉得必须要做到:
案例的层次结构一定要清晰
案例的检查点一定要明确
案例失败时一定要能精确的定位问题
案例执行结果一定要稳定
案例执行的时间一定不能太长
案例一定不能对测试环境造成破坏
案例一定独立,不能与其他案例有先后关系的依赖
案例的命名一定清晰,容易理解
案例的可维护性也是非常重要,如果做到上面的8点,自然也就做到了可维护性。下面来分享一下我对于上面8点的经验:
1. 案例的层次结构一定要清晰
所谓层次结构,至少要让人一眼就能分辨出被测代码和测试代码。简单的说,就是知道你在测什么。由于是进行
接口测试,我已经习惯了如下的案例层次:
DataDefine
我会将测试案例所需要的数据,以及数据之间的联系全部在预先定义好。测试数据与案例逻辑的分离,有利于维护和扩展测试案例。同时,GTest先天就支持测试数据参数化,为测试数据的分离提供了进一步的便捷。什么是测试数据参数化?就是你可以预先定义好一批各种各样的数据,而你只需要编写一个测试案例的逻辑代码,gtest会将定义好的数据逐个套入测试案例中进行执行。具体的做法请见:玩转
Google开源C++单元测试框架Google
Test系列(gtest)之四 - 参数化
SUT
SUT,即system under test,表明你的测试对象是什么,它可以是一个类(CUT),对象(OUT),函数(MUT),甚至可以是整个应用程序(AUT)。我单独将这个层次划分出来,主要有两个目的:
(1)明确的表示出你的测试对象是什么
(2)为复杂调用对象包装简单调用接口
明确表示测试对象是什么,便于之后对测试案例的维护和对测试案例的理解。同时,对于一些被测对象,你想要调用它需要经过一系列烦琐的过程,这时,就需要将这一烦琐的调用过程隐藏起来,而只关注被测对象的输入和输出。
TestCase
测试工程中,必须非常明确的表示出哪些是测试案例,哪些是其他的辅助文件。通常,我们会在测试案例的文件名加上Test前缀(或者后缀)。我建议,将所有的测试案例文件或代码放在最显眼的地方,让所有看到你的测试工程的人,第一眼看到的就是测试案例,这很重要。
Checker
对于一个复杂系统的接口测试,仅仅坚持输入和输出是远远不够的。比如测试一个写数据库的函数,函数的返回值告诉你数据已经成功写入是远远不够的,你必须亲身去数据库中查个究竟才行。因此,对于某一类的测试案例,我们可以抽象出一些通用的检查点代码。
如果做到上面的分层,那么一个测试案例写出来的结构应该会是这个样子:
TEST(TestFoo, JustDemo) { GetTestData(); // 获取测试数据 CallSUT(); // 调用被测方法 CheckSomething(); // 检查点验证 } |
这样的测试案例,一目了然。
2. 案例的检查点一定要明确
一定要明确案例的检查点是什么,并且让检查点尽量集中。有一个不好的习惯就是核心的检查点在分布在多个函数中,需要不断的跳转才能了解到这个案例检查了些什么。好的做法应该是尽量让检查点集中,能够非常清晰的分辨出案例对被测代码做了哪些检查。所以,尽量让Gtest的ASSERT_和EXPECT_系列的宏放在明显和正确的地方。
3. 案例失败时一定要能精确的定位问题
测试案例失败时,我们通常手忙脚乱。如果一个测试案例Failed,却不能立即推断是被测代码的Bug的话,这个测试案例也有待改进。我们可以在一些复杂的检查点断言中加入一些辅助信息,方便我们定位问题。比如下面这个测试案例:
int n = -1; bool actualResult = Foo::Dosometing(n); ASSERT_TRUE(actualResult) |
如果测试案例失败了,会得到下面的信息:
Value of: actualResult Actual: false Expected:true |
这样的结果对于我们来说,几乎没有什么用。因为我们根本不知道actualResult是什么,以及在什么情况下才会出现非预期值。因此,在断言处多加入一些信息,将有助于定位问题:
int n = -1; bool actualResult = Foo::Dosometing(n); ASSERT_TRUE(actualResult) << L"Call Foo::Dosometing(n) when n = " << n; |
4. 案例执行结果一定要稳定
要保证测试案例在什么时候、什么情况下执行的结果都是一样的。一个一会成功一会失败的案例是没有意义的。要保证案例稳定性的方法有很多,比如杜绝案例之间的影响,有时候,由于前一个案例执行完后,将一些系统的环境破坏了,导致后面的案例执行失败。在测试某些本身就存在一定几率或延时的系统时,使用超时机制是比较简单的办法。比如,你需要测试一个启动Windows服务的方法,如果我们在调用了该方法后立即进行检查,很可能检查点会失败,有时候也许又是通过的。这是因为Windows服务由Stop状态到Running状态,中间还要经过一个Padding状态。所以,简单的做法是使用超时机制,隔断时间检查一次,直到超过某个最大忍受时间。
ASSERT_TRUE(StartService('xxx')); int tryTimes = 0; int status = GetServiceStatus('xxx'); while (status != Running) { if (tryTimes >= 10) break; ::Sleep(200); tryTimes++; status = GetServiceStatus('xxx'); } ASSERT_EQ(Running, status) << "Check the status after StartService('xxx')"; |
5. 案例执行的时间一定不能太长
我们应该尽量让案例能够快速的执行,一方面,我们可以通过优化我们的代码来减少运行时间,比如,减少对重复内容的读取。一方面,对于一些比较耗时的操作,比如文件系统,网络操作,我们可以使用Mock对象来替代真实的对象。使用GMock是一个不错的选择。
6. 案例一定不能对测试环境造成破坏
有的案例需要在特定的环境下来能执行,因此会在案例的初始化时对环境进行一些修改。注意,不管对什么东西进行了修改,一定要保证在案例执行完成的TearDown中将这些环境都还原回来。否则有可能对后面的案例造成影响,或者出现一些莫名其妙的错误。
7. 案例一定独立,不能与其他案例有先后关系的依赖
任何一个案例都不依赖于其他测试案例,任何一个案例的执行结果都不应该影响到别的案例。任何一个案例都可以单独拿出去正确的执行。所以,不能寄希望于前一个案例所做的环境准备,因为这是不对的。
8. 案例的命名一定清晰,容易理解
案例的名字要规范,长不要紧,一定要清晰的表达测试案例的用途。比如,下面的测试案例名称都是不好的:
TEST(TestFoo, Test) TEST(TestFoo, Normal) TEST(TestFoo, Alright) |
比如像下面的案例名称就会好一点:
TEST(TestFoo, Return_True_When_ParameterN_Larger_Then_Zero) TEST(TestFoo, Return_False_When_ParameterN_Is_Zero) |
阅读本文前建议先阅读云梯跨机房方案介绍,了解云梯跨机房项目背景,难点以及解决方案。本文重点介绍下跨机房
测试的整体解决方案
测试用例管理:http://kelude.taobao.net/testsuites?project_id=12202
数据安全性测试
数据安全简单的说就是不能丢数据,跨机房后集群规模到达近万台,数据存储到达数百PB,如何确保数据安全是一个很大的挑战
在跨机房的情况下,我们通过(Sange、Slive、DFSIO)模拟线上比例的各种混合性操作,通过NN的FSCK, CN的 CrossCheck工具定位异常数据如CORRUPT,一直处于BEING WRITTEN无法关闭的文件,跨机房失败的文件
数据安全性测试里需要考虑的一种重要场景是NN和DN重启,在实际的升级过程中,在集群重启前各业务线并没有停止读写数据,重启后数据的一致性和可恢复性非常重要;在实际的跨机房测试中我们曾发现一个因为重启后状态不一致导致无法加载EDITLOG从而使NN无法启动的BUG,如果这种问题发生到线上,后果不堪设想
性能测试的关键点是如何建立性能基准,对线上性能进行准确评估,跨机房测试性能基准工具主要包括:
DFSIO: HDFS I/O(读写) 性能基准
Slive: 主要模拟线上各种RPC,在每个TASK发起混合型RPC操作,并可以指定文件和block大小,该工具可以同时测试NN和DN的性能;
Sange: 主要模拟线上各种RPC操作压力,在每个TASK启动大量Thread进行混合性RPC操作 ,对NN产生压力,进而评估NN RPC 处理能力;SANGE工具不能指定文件和Block大小,会产生大量小文件,比SLIVE对NN可以产生更大的RPC压力
Terasort:MapReduce 数据排序能力基准
Gridmix :Gridmix和Rumen结合可以模拟和生产作业相应的负载,更真实的模拟生产环境
SmallJobBench:通过创建大量sleep job到不同group来测试jobtracker性能
SubmitJobBench:通过每个map启动(-nThreads)个线程,每个线程顺序提交(-nTasks)个作业, 每个线程有自己的独立账号,来模拟线上多账号并行度2K情况下JTProxy性能
跨机房性能主要对比场景:
主要针对上述场景进行性能对比和评估包括NN带宽,内存,CPU load, NN RPC 指标, NN 各个operation的吞吐量(opsper second)和平均执行时间, NN同步editlog性能指标,跨机房带宽流量,跨机房复制速率,跨机房副本删除速率,CROSSFSCK时间
跨机房测试还有很重要的一点是要保证计算一定优先从本机房读数据,除非本机房没有数据才会跨机房读;实际测试中TERASORT跨机房读对比本机房读性能会有32%左右的下降,而且对带宽也是很大的浪费。
重要性能指标参考:
1. cpu_user 2. cpu_wio 3. mem_free 4. bytes_in 5. bytes_out 6. load_five 7. total-iops 8. total-ReadBytes 9. total-WriteBytes 10. dfs.namenode.Syncs_avg_time 11. dfs.namenode.Syncs_num_ops 12. dfs.namenode.Transactions_num_ops 13. rpc.metrics.RpcProcessingTime_avg_time 14. rpc.metrics.RpcQueueTime_avg_time 15. rpc.metrics.RpcProcessingTime_num_ops 16. rpc.metrics.delayedCallsQueueLen 17. rpc.metrics.RpcQueueTime_num_ops 18. rpc.metrics.addBlock_num_ops 19. rpc.metrics.append_num_ops 20. rpc.metrics.create_num_ops 21. rpc.metrics.delete_num_ops 22. rpc.metrics.getFileInfo_num_ops 23. rpc.metrics.getListing_num_ops 24. rpc.metrics.listCorruptFileBlocks_num_ops 25. rpc.metrics.mkdirs_num_ops 26. rpc.metrics.rename_num_ops 27. rpc.metrics.RpcProcessingTime_num_ops 28. rpc.metrics.RpcQueueTime_num_ops 29. rpc.metrics.blockReport_num_ops 30. rpc.metrics.blockReceivedAndDeleted_num_ops 31. rpc.metrics.sendHeartbeat_num_ops 32. rpc.metrics.rollEditLog_num_ops 33. rpc.metrics.getBlockLocationsHA_num_ops 34. rpc.metrics.getBlockLocations_num_ops |
压力测试
压力测试和性能测试关系紧密,压力测试更侧重于在线上最大压力的情况下系统是否可以正常工作,性能测试则侧重于新上线的版本是否有性能下降问题,主要是基于基准进行性能对比
压力测试比较难的一点是如何在小规模的测试集群模拟线上真实的压力,基本思路是 启动MR程序, 每个Task启动多个Thread,在每个thread进行大量模拟操作,NN和DN压力测试我们可以用到SANGE和Slive,JTPROXY压力测试可以用到SubmitJobBench,JT压力测试可以用到SmallJobBench和GurgleClient(TaskTracker模拟器)
跨机房压力测试主要是评估crossnode的自身压力和crossnode对namenode、datanode的压力影响;crossnode对namenode压力主要体现在RPC的请求,对datanode的压力目前主要体现在带宽和磁盘上
数据准备:利用SLIVE工具产生跟线上一样数量的Block和文件数
跨机房压力主要场景:
稳定性测试
构建BI仿真实验室,模拟整个云梯跨机房项目变更流程,运行BI线一日的作业,查看作业运行情况和做数据产出对比,验证跨机房后数据的正确性和业务线运行时间是否受影响
总结
整个项目过程中,测试人员共发现有效bug 100多个,其中5个bug严重影响性能,10个bug可能导致丢数据,4个bug会导致服务不可用
本文主要是跨机房的测试整体介绍,整个跨机房测试是一个非常复杂的过程,可能大家觉得不是很过瘾,后续会进行跨机房测试经典BUG和工具分享,敬请期待
一、对于你的问题,首先明确测试类型,然后才能明确自动化测试类型,最后定位哪个类型用哪个方面的自动化测试工具。 2、不同的测试类型使用的自动化测试方法不同,白盒测试主要针对代码级的
单元测试、黑盒测试主要面对功能级和系统级的验证测试。
3、自动化测试,针对白盒测试,一般需要有一定的编程基础,即能够基于功能代码写测试代码,常用的单元测试方面的自动化测试工具很多,上网一搜全是。
4、自动化测试,针对功能测试,有几种情况,基于CLI、API和GUI的测试;基于CLI、API的测试,即应用脚本技术向设备模拟发送CLI命令或者API请求,以达到控制设备的效果。基于GUI功能测试,即应用传统的界面自动化测试工具(例如:RFT、
QTP等)控制界面控件操作的方法,以达到模拟用户操作,这几种方式都需要你有一定的编码基础;基于CLI、API的需要你懂脚本技术(例如:tcl、python、ruby等),RFT需要你懂
java或者.net、QTP需要VB等。
5、你说的loadrunner就是性能测试方面的工具,即是测试软件性能、例如多用户操作等性能、也需要写代码,LR脚本支持的语言有:java、c、Visual Basic、vbscrīpt。默认的脚本生成语言为 C;其实我想说的是,性能测试工具不重要,你需要掌握其性能测试的方法才是更重要的。
二、我感觉你想入门自动化测试,但是从你问的问题来看,有一定盲目性,我简单说一下自动化测试吧。
1、自动化测试,其理念就是应用各种手段模拟人工操作,节省人力测试成本,保证产品测试质量。
2、你想学好自动化
软件测试,不是单单靠
学习几个自动化工具就能掌握的,但是你可以从工具入手,首先,告诉你自动化测试的基础是:
1)编程技巧,包括高级语言和脚本语言,脚本语言是初期的掌握,可以有,tcl、phython、ruby等而高级语言,要好好学好一门,例如,我是对java为重点。还有,如果你是对
web自动化测试的话,那么jsp、php、HTML、CSS等web语言是必须掌握的。
2)
操作系统技巧;因为软件自动化测试是构建在操作系统上的,其技巧需要能善于利用到操作系统的各种技巧,例如:注册表、环境变量、句柄等。
4)业务知识,这也是重点。你所在软件行业的软件业务,要知道你的软件的
工作方式。
5)质量与流程管理理念。
然后,你的学习步骤:
1)可以从工具入手,根据具体的项目去学习;例如:java软件界面测试(RFT、QTP的java插件等)、web界面测试(QTP、selenium等)、性能测试(RPT、loadrunner等)。但记住,学习其工具,重点不是简单的使用,而是如何利用工具去扩展。
2)然后,重点学习以上的基础,以编程为重点,其余的结合学习,顺便说一句,其实自动化测试的理念与软件设计模式理念很像,你可以从中有所领悟。
3)之后,再学习去拓建自己的自动化测试框架,何谓框架,一下说不清楚,我给你推荐一下。
注意:如果没有自动化测试方面的实践项目的话,最好先从基础学起,因为基础学好了,自动化测试入门会很快的。
4(至于性能测试,也是一样,可以先从工具入手,但不要局限于工具,性能测试最重要的是环境的构建方法以及对测试结果的分析方法,所以性能测试重点在于分析和实现过程,而不是工具使用过程。
在
QTP中,要先去录制用户名和密码,错误的一次和正确的一次,对于错误所弹出的对话框要进行建立文本的检查点,以便datatable的用例进行比对。用户名和密码要进行参数化,在进行判断。
Dim url url="C:\Program Files\Mercury Interactive\QuickTest Professional\samples\flight\app\flight4a.exe" if Dialog("text:=Login").Exist(1) then OptionalStep.Dialog("text:=Login").WinEdit("attached text:=Agent Name:").Set DataTable("p_Text", dtGlobalSheet) OptionalStep.Dialog("text:=Login").WinEdit("attached text:=Password:").Set DataTable("p_Text1", dtGlobalSheet) OptionalStep.Dialog("text:=Login").WinButton("text:=OK").Click wait(1) else |
systemUtil.Run url OptionalStep.Dialog("text:=Login").WinEdit("attached text:=Agent Name:").Set DataTable("p_Text", dtGlobalSheet) OptionalStep.Dialog("text:=Login").WinEdit("attached text:=Password:").Set DataTable("p_Text1", dtGlobalSheet) OptionalStep.Dialog("text:=Login").WinButton("text:=OK").Click wait(1) end if |
检查点
if Dialog("text:=Login").Dialog("text:=Flight Reservations").Exist(2) then dim errorinfor,odesc set desc=description.Create odesc("text").value=DataTable("error",dtGlobalSheet) errorinfor=Dialog("text:=Login").Dialog("text:=Flight Reservations").Static(odesc).GetROProperty("text") Dialog("text:=Login").Dialog("text:=Flight Reservations").WinButton("text:=确定").Click |
Reporter.ReportEvent micDone,"登陆验证失败,提示信息如下",errorinfor OptionalStep.Dialog("text:=Login").close wait(1) else |
如果用户名和密码正确就进入订票页面
If Window("text:=Flight Reservation").Exist(10) then Reporter.ReportEvent micPass,"登陆验证通过","用户名密码正确" Window("text:=Flight Reservation").close wait(1) Set desc=Nothing end if end if |
飞机登录描述性编程图片
测试结果
版权声明:本文出自 shujin6040 的51Testing软件测试博客:http://www.51testing.com/?597535
原创作品,转载时请务必以超链接形式标明本文原始出处、作者信息和本声明,否则将追究法律责任。