1、  背景

本次性能测试的系统是X银行营销服务系统总行版,该系统使用的数据库服务器、应用服务器均布署在总行机房,各地分行通过 WEB 方式登录访问本系统。系统上线后的总用户数(包括各分行、支行主管,客户经理等)在 5000 左右。

该系统采用 DB2 数据库、 WebLogic 应用服务器。

本次性能测试进入的条件是系统的代码已经基本完成并经过功能测试。

2、  测试计划

在确定了本次性能测试的要点后,我们初步拟定一份性能测试计划,提交给客户,并获得了客户的认可。在本文中不列出项目测试计划中的所有内容,仅就主要问题进行说明。

测试范围:在真实业务局域网测试环境下,对系统实施并发性能测试的同时,监控 Web 服务器和数据库服务器的系统资源,以及数据库资源的使用情况。

测试内容:并发性能测试、系统资源监控。

测试方法与工具:采用自动测试与人工测试相结合的测试方法,测试工具使用 LoadRunner

测试资源:测试环境及测试数据准备。

3、  测试用例

确定了测试计划,我们针对该系统的特点,从中挑选出三个有代表性的功能点,作为本次性能测试的用例。我们认为作为银行的营销服务系统,最常使用且对于系统的整体性能有着较大影响的是“客户信息查询”和“客户对账单查询”两个模块。因此,我们设计了三个单交易性能测试用例,分别是:“用户签到 / 签退”、“客户信息查询”、“客户对账单查询”。然而客户却对此提出异议,他们认为我们设计的测试用例数量太少,要求我们的测试用例应包含更多的功能模块。经过会议讨论,最终我们根据客户给出的一份性能测试大纲,针对其中提出的测试内容、测试策略,以及测试目标,将单交易测试用例增加到十四个。

测试用例采用以下格式:

案例名称

并发

用户数

数据量

操作步骤

备注

       要求清晰地描述出详细的操作步骤。

4、  测试数据

针对以上设计的测试用例,需要准备大量的业务数据。本次性能测试的环境即系统上线后真实运行的环境,所有的业务数据均来自兴业银行的真实核心系统(通过 ETL 转换),数据量已经能满足测试的需要。

由于测试用例中要求执行并发操作的时候使用不同身份的用户登录系统,因此在测试开始前需要准备一批具有不同身份的用户名(包括各分支行的主管以及客户经理),并且要有相应的操作权限。

对于“积分转移”、“积分兑换”、“礼品兑换”等等交易,则需要提供一批卡上有足够积分的客户理财卡号。

以上测试数据由兴业银行负责提供,在性能测试执行之前提供给我们。

5、  测试脚本

使用性能测试工具 LoadRunner 录制并调试测试脚本,对相关的输入项进行参数化。

6、  测试实施

LoadRunner 中执行测试脚本,实施性能测试。对于每个单交易测试脚本各执行一轮测试,并按一定的用户比例设计出一个混合交易场景,令其自动持续运行五小时左右,观察系统的性能表现。每次执行的结果文件均保存下来,待测试完成后连同性能测试报告一并交付客户确认。在此过程中,需要监视相关的系统资源使用情况,包括:应用服务器和数据库服务器的所有系统资源指标,所有数据库资源指标。

7、  测试结果

经过本次性能测试,发现了系统五个主要的性能问题。我们与程序开发人员一同分析问题产生的原因,并给出改进建议,一起记录到测试报告中。其中的一个问题在性能测试报告提交客户之前已经过优化,得到显著改进。

8、  测试结论

测试结果显示,系统性能能满足测试目标, 交易并发数达到或超过30个,批量交易(查询记录50条以上的交易)并发数也能达到或超过10个,交易平均响应时间在212秒内,90%平均响应时间在215秒间完成。

混合交易案例持续运行 5 小时,运行结果正常,系统没有报任何错误,系统稳定, 可用率应达到100

另外如在ETL批处理期间运行 营销服务系统 ,系统性能明显下降,建议ETL批处理在夜间处理,避免影响 系统的正常运行

9、  经验

在本次性能测试的过程中,我们遇到一些问题,通过解决这些问题,从中获得了一些经验。现总结如下:

l         问题一

在我们对系统进行测试的过程中,某些操作是相关联的。例如我们测试“查看客户资产历史”这个交易的系统响应时间,这时需要先列出客户的基本信息,选中一个客户,点击打开另一个页面,才能查看到该客户的资产历史信息,同时,在测试脚本中需要对所选择的客户编号做一个参数化,但由于 LoadRunner 不提供像 WinRunner QTP 一样识别页面对象的功能,如果在 Vugen 中直接抓取页面上显示的客户编号去参数化,实现起来将十分烦琐。考虑到在以上那两步操作中,第一步“列出客户基本信息”只是辅助的操作,而第二步操作“查看客户资产历史”才是我们要测试的功能点,因此我们忽略了这二者之间的关联性,仅对第二步操作中的客户编号进行参数化。(只要服务器端对此不加验证,甚至我们将第一步操作都忽略掉,也是可行的)。

结论: LoadRunner 的工作原理是根据所选择的协议组装成相应的报文在前后台之间通讯,以此达到模拟实际操作的目的,因此我们只需将要测试的交易或功能点所需要组装的报文传送给后台服务器即可(因为我们关注的只是系统的性能,不是功能),而不必像功能测试那样,按部就班地重现每一步操作。

l         问题二

在测试过程中,我们发现有一个查询交易的系统响应速度特别慢,无论是在 Controller 中使用单个虚拟用户执行脚本,还是在 Vuser 中直接运行,情况均是如此,然而当我们用手工进行同样操作的时候,响应时间却明显地小于工具统计出来的时间,也就是说,在 LoadRunner 中模拟操作的结果与真实操作的结果明显不一致。经过反复尝试与观察,我们才终于找到问题的原因所在:该查询交易是通过客户的证件号码查询客户信息,当用户输入客户的证件号码时,如果没有选择证件类型,系统会自动将证件类型设置为默认值“身份证”。按“证件类型 + 证件号码”为组合索引查询客户信息表,查询速度极快,而在我们录制脚本时,忽视了“证件类型”这项输入,只有“证件号码”,因此查询的效率大为降低。解决办法:只需在测试脚本中,对 CertType (“证件类型”)一项赋值为“ A ”(“身份证”),此时在 LoadRunner 中重新运行该脚本,响应速度提高,统计结果与实际完全一致!

结论: LoadRunner 的工作原理是根据所选择的协议组装成相应的报文在前后台之间通讯,以此达到模拟实际操作的目的,因此我们在测试脚本中组装发送到服务器端的报文时,注意一定要和实际操作时的发送报文完全一致,这样才能保证测试的结果与真实情况相吻合。这就要求在测试正式开始执行时,要对测试脚本进行反复的调试,通常的做法是:在 Vugen 中执行一遍脚本,统计执行某个事务的时间,再用手工实际做一遍同样的操作,大体上比较一下,确保与实际估算的时间没有太大出入后,再逐渐增加并发用户数,正式开始性能测试。

l         问题三

在我们的每个测试脚本中的 init 部分,都录制了登录系统的操作,并且对登录的用户名进行了参数化,使用各种不同身份的用户(分行主管、支行主管、客户经理等)进行相同的操作。在并发测试过程中发现对某些查询交易测试的结果波动较大,系统响应时间从零点几秒到几十秒不等。经检查后发现原因在于:使用不同身份的用户登录系统后,在输入查询条件后,点击查询按钮后会将根据该用户的身份,将其所属的分行机构号、支行机构号、客户经理编号等一并提交,因此在脚本中,就必须根据不同的用户身份,分别将其对应的分支行机构号等也运用参数提交,否则在执行脚本时就会出现查询不到记录或查询速度变慢等各种问题。解决方法有三个: 1 、修改脚本,使其能够依据用户的身份分别传送相应参数, 2 、针对不同类型的用户使用不同的脚本分别测试。 3 、输入参数使用统一的用户类型。在实际中,我们为了简化脚本的复杂度,节省对脚本编程的时间,采取的是第三种方法。

结论:由于 LoadRunner 的工作原理是根据所选择的协议组装成相应的报文在前后台之间通讯,因此它会跳过在应用程序前台进行的校验,所以在脚本回放的时候一定要注意在脚本中提前进行这些校验或改由人工控制,以保证发送报文的正确性(如操作权限的控制等)。

l         问题四

测试多用户并发登录系统的时候,从虚拟用户图和事务图上发现,总有一部分用户在登录的时候要等待很长时间,用户登录的最小时间与最大时间相差非常大。于是我们在让脚本自动运行的同时,手工再登录一个用户,发现无论如何都不会发生等待的情况,多次试验的结果均是如此,也就是说 LoadRunner 测试的结果与实际结果再次发生了偏差!经过反复的调试,以及与程序开发人员沟通,我们终于发现问题的原因所在:在用户登录系统的时候,系统会自动记录登录用户的信息,并产生一个登录 ID ,以此 ID 做为主键,向数据库插入记录。因此当大量用户在极短的时间内同时登录时,就有可能出现生成相同的登录 ID 的情况,此时便会造成数据库中的主键冲突,导致用户等待很长时间或登录失败。而我们手工测试时却无法做到在很短的时间内同时登录,因此很难用手工重现此种情况。通过 LoadRunner 的模拟表现出来的状况,正是我们测试出程序存在的性能问题,并非与实际结果的偏差。

还有一个例子,在第二轮性能测试中,同样发生了类似的情况。在本系统中,如果同一个用户登录后,未正常退出超过 5 次,系统将会把该用户锁住,使其无法再次登录,而我们用于参数化的用户名个数有限,因此当脚本使用大量用户同时登录时,很容易造成同样的用户登录系统而未签退的情况发生(脚本正在执行,还未能退出),此时将会造成许多用户操作的失败。

结论:使用 LoadRunner 可以模拟出大量用户同时对系统操作的情况,而这些情况通过手工往往是很难重现出来的。例如大量用户在同时对系统做某些操作时,很容易造成数据库的死锁、锁等待、主键冲突、数据混乱等等问题,因此在做性能测试的同时,也常常可以连带测试出系统的一些隐蔽的“缺陷”。在本次性能测试中,这种例子是很多的。对待此类“缺陷”,应具体情况具体分析。有些确实是程序的 BUG ,需要修正,而有些可能只是很极端的情况,只有在做压力测试时才有可能发生,可不必深究。

l         问题五

此问题发生在第二轮测试(即回归测试)中。在第一轮测试中发现的性能问题,经程序员修正后,我们对系统进行了第二轮性能测试,以验证其性能改进的效果。在前一轮测试中,我们发现通过选择客户级别为“未评级”时,查询的速度极慢,经过改进后,速度应有较大提高。然而在回归测试中,却依然很慢。经过对测试脚本和程序的仔细检查,才发现原来在程序中已将“未评级”这个选项去除,而我们的测试脚本的参数文件中仍然保留有该选项,因此测试的结果与前次没有区别。在参数文件中将该选项去掉后,测试结果正常,查询效率有所提高。

结论:使用录制好的测试脚本进行回归测试之前,一定要先仔细检查、了解程序的改动,对原先的测试脚本做必要的修改后,才可以重新测试,否则只是在做无用功。

10、              教训

在本次测试过程中,由于经验不足,我们也得到了一些教训。前事不忘,后事之师,现总结出来与大家分享。

l         与客户的沟通做得不够,客户要求我们做的性能测试用例数量太多,我们未能据理力争,最后导致工作量过大。

l         按照原定的项目计划,我们要在系统的功能测试即将结束前进驻项目组,准备并进行性能测试。然而由于客户在功能测试的后期仍然不断的提出新需求,导致开发人员疲于奔命,系统的性能难以稳定下来,性能测试的前期准备工作也受到很大影响,不能正常开展,浪费了很多人力物力。

l         由于客户无法提供一个单独的性能测试环境,我们的性能测试工作与业务组的功能测试在同一个环境下进行,而系统的功能测试迟迟未能完成,加上 ETL (数据转换)小组对数据库资源的占用,因此我们的性能测试只能在夜间才能进行。导致时间上的浪费,使项目的成本增加。

l         没有将性能测试中发现的缺陷记录到缺陷管理工具中加以跟踪,而仅仅体现在最后的测试报告上,个人认为这是比较不规范的做法。

l         性能测试前的数据准备不够充分。客户提供测试的系统用户、身份数量有限,导致许多案例的测试只能使用少量数据进行参数化,由此带来许多本可以避免的问题。

l         测试计划及测试报告的书写格式缺乏规范,尤其测试计划书未能包含本应包含的所有内容。

l         在我们将 LoadRunner 的测试结果文件全部提交给客户的前提下,客户仍然要求我们在测试报告中将每一次测试的数据均以表格的形式填至测试报告中,此项工作的工作量十分巨大,个人认为这样做并无必要。

以上是在本次性能测试及回归测试过程中总结出来的一些经验教训,在此做一个小小的总结,以便下次工作中改进。