测试经验经典.docx
- 文档编号:27230649
- 上传时间:2023-06-28
- 格式:DOCX
- 页数:11
- 大小:24.91KB
测试经验经典.docx
《测试经验经典.docx》由会员分享,可在线阅读,更多相关《测试经验经典.docx(11页珍藏版)》请在冰豆网上搜索。
测试经验经典
自InfoQ翻译了JurgenAppelo的“软件开发者面试百问”,该文受到了许多读者的欢迎。
在2009年5月刊的《程序员》中,周伟明先生对其中的“项目管理”和“软件测试”部分的题目进行了回答。
笔者不才,也想试着给出自己的答案。
既然是“面试”,那么作答时,便不参考其他资料,只凭记忆和经验撰写。
1、什么是回归测试?
怎样知道新引入的变化没有给现有的功能造成破坏?
回归测试是指,运行一组测试用例(称作“回归测试用例集”)来检查代码的变化没有破坏已有的功能。
软件开发是一个演进的过程,需要不停地重构和增加代码。
回归测试提供了修改的正确性反馈,为稳定的开发流程提供了安全网。
在理想的情况下,应该将所有测试用例组成回归测试用例集,来检查修改的正确性。
不过,受到资源的限制,开发者往往选择与修改相关的测试用例来运行回归测试。
例如,被修改类的所有单元测试用例、被修改模块的所有组件测试用例、被修改系统的所有功能性测试用例等。
在我的工作中,我通过以下方法来检查代码变化的正确性。
●利用每小时一次的rollingtest来检查最近的签入(check-in)没有造成构建失败(buildbreak)、部署失败、端到端系统测试(end-to-endsystemtest)失败。
这可以及时地发现阻碍每日测试的blockingbug。
●利用每日测试(dailytest)来运行所有的自动化测试用例集。
●通过代码比较工具,检查前后版本的代码。
一方面,通过代码检查来挖掘可能的问题;另一方面,可以有针对性地补充一些测试用例,以覆盖新的逻辑。
●在恰当的时候,再次运行手工测试用例集。
由于我的测试用例集的自动化程度较高,我通常在里程碑(milestone)快结束前运行手工测试用例集。
●代码变更往往造成测试失败(testbreak),需要重构测试代码来适应新的接口和实现。
采用良好的测试实现模式,可以降低测试维护的代价。
2、如果业务层和数据层之间有依赖关系,你该怎么写单元测试?
严格地说,单元测试不应该访问外部资源(包括文件、网络、数据库等)。
单元测试的目的是提供快速的正确性反馈,使“测试—开发—重构”的迭代循环达到“流”状态。
依赖于外部资源的单元测试运行缓慢,可能给开发带来负面影响。
如果测试运行超过1分钟,许多时间被浪费在等待上,开发者很难到达“流”状态,开发效率会隐式地降低。
如果测试运行超过5分钟,开发者可能会选择偶尔运行单元测试,以加速开发速度。
这将大大削弱单元测试作为安全网的效果,进一步降低开发效率。
如果业务层和数据层有依赖关系,开发者还是应该尽量使单元测试不访问外部资源。
我会使用以下策略来达到这一目标。
1)在设计上,将业务层和数据层的依赖限制在最小范围内。
一个可能的方案是,将依赖封装在一个类中,例如DataManager。
在业务层,只有少量代码依赖于它,而大多数代码不依赖于数据层。
对于数据层无关的业务层代码,可以方便地编写单元测试。
2)对于依赖于DataManager的类,可以采用伪对象(mockobject)测试模式来编写单元测试。
对于C#等静态语言,需要使用提取借口(extractinterface)的重构手法,获得DataManager的接口IDataManager。
然后编写符合该接口的伪对象,例如DataManagerMock。
DataManagerMock不访问数据库,它使用内存中的数据来模拟数据层操作。
测试方法用依赖注入(dependenceinjection)策略,使被测试的业务对象依赖于受控的DataManagerMock,这样就可以编写只访问内存对象的单元测试了。
以下代码演示了这种方法的思路。
1:
//Businessclassundertest
2:
classEmployee{
3:
IDataManagerdata;
4:
Business(IDataManagerdata){
5:
this.data=data;
6:
}
7:
...
8:
}
9:
10:
//Mockobject
11:
classDataManagerMock:
IDataManager{
12:
//databaseinmemory
13:
Hashtableemployees=newHashtable();
14:
...
15:
}
16:
17:
[TestMethod]
18:
voidTestEmployee(){
19:
//setup
20:
IDataManagerdata=newDataManagerMock();
21:
Employeee=Employee(data);
22:
//testobjectewithdata
23:
...
24:
}
3)除了伪对象,还可以采用测试专用子类(testspecificsubclass)、依赖注册与查找(dependenceregisterandlookup)、动态类型(ducktyping)等策略,使业务对象在测试方法中不依赖于DataManager,而是依赖于不访问数据库的伪数据层对象。
这里的关键是,解除业务层与数据层的依赖,在测试方法中,将数据层对象替换为测试专用对象(testspecificobject)。
4)有时,访问数据库是难以避免的。
在这种情况下,应该为每一位开发者和测试者建立数据库沙箱(sand-box),使得他们的工作相互独立。
在测试方法中,先用数据操作脚本将数据库恢复到指定状态,然后再执行测试逻辑。
通常,我会从产品数据库中选择少量数据,构成一个典型的数据集,用于初始化数据库。
这样既体现了产品数据库的特性,也尽可能地提高了测试速度。
3、你用哪些工具测试代码质量?
在实际工作中,我通过以下工具来检查代码质量。
●单元测试工具。
当我编写IronPython代码时,我使用自己编写的单元测试框架IpyUnit。
它只依赖于.NETFramework2.0,并适用于IronPython1x和IronPython2x。
对于C#代码,我使用VSTSUnitTestFramework。
对于C++代码,我使用过CppUnit,后来使用一个自己编写的单元测试框架,其实现思路与CppUnitLite相似。
自我辩解一下:
我没有重复发明轮子的倾向。
编写IpyUnit是因为现有的Python单元测试框架依赖于CPython程序库,而我需要一个只依赖于.NETFramework的测试框架。
编写自己的C++单元测试框架是受到一些公司政策的要求。
●代码静态分析工具。
HerbSutter在《C++CodingStandards》中建议在高警告水平做干净的编译(cleanbuild)。
因此,我会努力使我的代码没有编译警告。
此外,我还使用VisualStudio内建的FxCop来检查.NET程序的质量。
●代码度量工具。
我写过一个Python脚本,扫描代码树(sourcetree)获得每一个模块(对应为代码树上的目录)有多少文件、有多少行代码。
这是一个非常简单的工具,可以提供代码规模与分布的概要信息。
利用这些信息,可以为测试计划的制定提供指导。
例如某个模块的代码很多,测试却相对较少,那么应该考虑为它增加更多的测试用例。
VisualStudio2008内建了代码复杂度度量工具,也可以为测试提供指导。
例如,某个类的复杂度较高,测试却相对较少,那么应该考虑为它增加测试用例。
●自制工具。
在性能测试、压力测试等活动中,需要自己编写一些工具来完成任务。
例如,编写一个客户端工具,它可以快速地向服务器提交大量的请求。
这有助于发现一些在压力情况下才可能出现的问题。
4、在产品部署之后,你最常碰到的是什么类型的问题?
在我的项目中,产品部署之后常遇到两个问题。
第一、客户认为系统的一些功能不满足需求。
这是一个普遍却严重的问题,我将在问题9的回答中讨论缓解方法。
第二、在大负载压力下,系统出现不稳定的症状,例如系统响应迟缓、服务重启等。
对于该问题,我考虑采用以下手段来缓解。
首先,了解系统在产品环境(productionenvironment)中的工作负载(workload)。
通过检查已有系统的日志,可以预测出新系统上线之后的工作负载。
基于历史数据的计算,而不是猜测,是预测准确的关键。
有时候,新系统提供了新的业务,没有历史数据可以分析。
开发者需要与客户密切合作,根据客户的业务需求来推测可能的负载。
也可以评估类似系统的负载,以推测被测试系统的负载。
总之,这不是一项简单的任务,最好用多种方式进行预测,然后综合得到系统的预期负载。
然后,根据预期负载,设置性能测试的测试目标。
在确立目标之后,可以进行下列测试来质疑(critique)被测试系统。
●性能测试(performancetest):
检查系统能否达到性能目标,能否在预期最大负载下正常工作。
所谓正常工作是指,系统的响应时间、吞吐量、资源占用、结果正确性等指标符合预期。
●负载测试(loadtest):
考察系统的性能指标随工作负载变化的情况,以检查系统的可伸缩性(scalability)。
●压力测试(stresstest):
使用远远大于最大预期负载的工作量来测试系统。
在这种压力下,系统会响应迟缓或丢失连接,但不应该发生资源泄漏、服务停止等无法挽回的问题。
当负载落回正常范围之后,系统的性能指标应该恢复正常。
在测试过程中,应该使用多种手段监控系统的运行情况。
一旦发现问题,如CPU资源占用过高,就应该展开分析。
性能测试是一个持续的过程,从开发早期一直延续到产品部署。
及时地发现问题并解决问题是性能测试成功的关键。
这世界上的许多事情都是知易行难。
反思最近一次产品开发,也是在以上几点有所不足。
第一、没有准确地预测出产品的实际负载。
第二、在测试过程中,没有模拟出一些在产品环境会发生的使用情景,使得有些问题在产品环境中才发现。
第三、没有从整体监控整个系统的性能。
对于分布式计算系统,不但需要检查子系统的性能,还需要从整体考察系统的吞吐量与瓶颈。
测试是一个学习的过程,希望这些反思会对未来的工作有所帮助。
5、什么是代码覆盖率?
有多少种代码覆盖率?
代码覆盖率用于评估测试用例覆盖代码的程度。
常见的代码覆盖率包括语句块覆盖、分支覆盖、函数覆盖、类覆盖等。
VisualStudio2008已经支持这些覆盖率度量。
●语句块覆盖(blockcoverage)评估测试用例的执行覆盖了多少语句块。
语句块是最大的单入口、单出口的顺序语句分组。
●分支覆盖(branchcoverage)评估测试用例的执行覆盖了多少分支。
如何统计分支,有不同的计算方法。
一般可以将语句块之间的可执行连接视作分支。
●函数覆盖(functioncoverage)评估测试用例的执行覆盖了多少函数。
●类覆盖(classcoverage)评估测试用例的执行覆盖了多少类。
当我还在大学的时候,研究过基路径覆盖(basispathcoverage)、条件覆盖(conditioncoverage)、决定性条件覆盖(MC/DC)等覆盖率度量。
不过,它们在工业界没有得到广泛的应用。
代码覆盖率可以检查出未被测试覆盖的代码,有助于揭示测试用例集的不足。
在实际工作中,我会不断地补充测试用例,使得语句覆盖率尽可能达到100%。
6、功能性测试和探索性测试的区别是什么?
你怎么对网站进行测试?
功能性测试侧重于检查系统是否符合规格说明所指定的功能性需求。
开发者可以根据规格说明,构造相应的测试用例,以检查系统是否实现了指定的特性(feature)。
在敏捷团队中,开发者和客户(或商业分析师)一起开发用户故事(userstory)。
这些故事被实现为端到端的功能性测试(end-to-endfunctionaltest),用于驱动开发和验收成果。
在敏捷开发中,大部分功能性测试应该是自动化的。
探索性测试是语境驱动学派(contextdrivenschool)所倡导的测试方法,强调根据当前测试的语境选择最有效的测试策略。
测试者选择一个测试目标,一边测试系统,一边学习系统的知识。
随着知识的积累,他会不停地调整测试策略,并使用多种方法来测试系统。
当测试目标达成,或者他认为出现了更有价值的目标,测试者会改变测试目标,展开新的测试。
探索性测试将测试视为一个不停地学习、尝试、反馈的过程,强调测试者的学习、思考、应变的能力。
因此,探索性测试只能是手工测试。
当然,测试者可以用自动化工具完成一些例行任务,例如安装软件、设置环境、准备数据等;他也可以开发自动化测试用例来复现探索性测试所发现的缺陷。
网站测试是一个复杂的问题。
如果只考虑功能性测试和探索性测试,可以考虑以下测试活动。
1)将网站划分为表现层和业务层。
业务层负责业务逻辑,接受表现层的输入,并提供表现层所需要的数据。
表现层接受用户的输入,并将业务层的数据以合适的方式展现给用户。
2)开发者与商业分析师一起确定用户故事。
开发者将其实现为自动化测试用例。
与业务相关的功能,应该在业务层编写测试用例。
与表现相关的功能,应该在表现层编写测试用例。
通常,在业务层实现测试的投入产出比较高,因此应该尽量分离表示逻辑和业务逻辑,使表示层承担的任务尽可能的少。
功能性测试用例提供了基本的质量保证,是进一步测试的基础。
3)在功能性测试相对稳定之后,可以开展探索性测试。
可以用浏览器访问网站,也可以用客户端工具直接访问业务层。
可以采纳情景测试(scenariotest)的方法,模拟真实用户去完成一个复杂的任务或构建一个真实的应用。
例如,如果测试在线文档处理网站,就用它去排版一篇包含多种字体、图形、格式的文档(收集一些博士学位论文可能是一个好主意)。
也可以用工具扫描网站,以发掘安全漏洞,然后用具体的测试用例来证实潜在的漏洞是可以被利用的。
总之,可以在功能性、易用性、安全性等多个角度探索系统。
7、测试套件、测试用例、测试计划,这三者之间的区别是什么?
你怎么组织测试?
测试计划(testplan)对测试活动进行整体规划。
它从以下几个方面辅助测试工作的展开。
●为测试工作进行技术准备。
测试计划应该给出测试目标和退出条件。
在此基础之上,给出测试策略,以指导具体的测试活动。
●对资源进行分配。
测试计划应该给出大致的测试日程,并根据日程分配软硬件资源。
●建立测试协作的基础。
测试计划应该制定大致的测试过程(testprocess)、明确的测试规范(testguideline)和具体的人员职责。
●识别测试风险,进行风险评估,并建立风险监测和预防机制。
在我看来,测试计划最重要的功能是提供团队协作的基础,记录团队已经达成的共识。
如果这些基础和共识发生了变化,那么测试计划也需要进行相应的修改。
测试用例(testcase)是一个已文档化的流程(documentedprocess),用于探测软件的缺陷。
对于单元测试,一个测试用例最好只检查一项内容,例如一个方法的一个特性,这使得测试用例思路清晰、易维护、易运行。
对于系统测试,一个测试用例往往实现了一个复杂的流程,该流程对应于一项用户活动,该活动实现了特定的用户价值。
对于性能测试,一个测试用例往往体现为一个应用场景,以及在该场景下对性能指标的要求。
可见,不同类型的测试用例有不同的表现形式。
通常,它是该类型测试的最小组织单位。
测试套件(testsuite)是测试用例的集合。
测试者根据测试需求将测试用例组织到测试套件中,以便更有效地运行测试、生成测试报表。
例如,将一个模块的所有测试组织到一个测试套件中,作为回归测试用例集。
如果该模块发生变化,则运行该回归用例集,以捕获可能的回归错误。
再例如,将按照功能特性(functionalfeature)划分测试套件,然后根据测试套件生成测试报表。
所得的报表按照特性组织,便于阅读和理解。
在实际工作中,我按照如下方式组织自动化测试。
1)一个测试方法(testmethod)只测试一项内容。
测试方法的命名规则是test_FeatureName_TestStartegy,例如test_CreateUser_NoneInput,这样可以快速地了解被测试的特性和所使用的测试策略。
2)将一个类或特性的所有测试用例都放在一个测试类(testclass)中,这样便于按照特性审查、维护、增加测试用例。
3)将一个测试夹具(testfixture)的所有测试用例都放在一个测试类中。
所谓测试夹具,是指完成测试所必需的被测试对象以及相关的业务对象、伪对象、桩对象等。
在一些情况下,需要大量的代码来建立测试夹具。
因此,按夹具来组织测试方法,可以复用夹具建立代码,提高测试开发效率。
4)将可复用的测试代码组织到测试辅助类(testutilityclass)或父测试类(supertestclass)中。
5)按照功能特性组织测试套件。
将一个特性所有的测试用例都放置在一个测试套件中。
将所有的测试套件组成测试用例全集,用于每日测试。
此外,我还组织了一个BVTsuite,用于每小时一次的rollingtest。
8、要对电子商务网站做冒烟测试,你会做哪些类型的测试?
冒烟测试用于快速地检查构建(build)的质量,发现阻碍进一步测试的严重缺陷。
在冒烟测试中,我倾向于运行若干系统测试,以检查系统的工作流是否运行正常。
这是基于这样一条前提:
开发者在签入代码之前已经运行过单元测试。
这时,错误更有可能发生在组件之间、在子系统之间。
对于电子商务网站,我会运行以下测试。
1)在一台干净的机器上,完整地部署网站。
部署应该成功。
2)一个已注册的客户可以下订单。
测试用例可以完成如下操作:
使用一个内建的测试账户登录系统,选择一件商品加入购物车,结算购物车中的商品,得到订单确认信息。
3)后台系统可以完成订单流程。
测试用例会检查后台系统正确地完成了如下操作:
从数据库中获得订单,修改订单状态为“接受”,进入配货阶段,进入发货阶段,修改订单状态为“完成”。
在我的冒烟测试中,测试用例关都注于最重要的正常流程(happypath),更复杂和全面的测试留待后续测试活得完成。
以上测试考察了订单处理的流程。
如果需要,还可以补充一些系统测试用例,以检查用户注册、用户评论、商品推荐、后台统计报表生成等功能。
9、客户在验收测试中会发现不满意的东西,怎样减少这种情况的发生?
用户在验收测试测试中表示不满意,这通常是因为软件没有满足他的需求。
以下手段有助于更好地满足用户需求。
1)在捕获初始需求时,搞清楚用户的业务目标。
许多时候,开发者混淆了用户的目标和任务,一方面没有提供有力的业务支持,另一方面设置了条条框框,使得用户难以方便地实现业务价值。
2)在规格说明中,排除催眠词汇。
所谓催眠词汇是指“有效”、“快速”、“友善”、“加速”等让人舒服又不知所云的修饰语。
它们应该被量化的陈述所取代:
对于10个并发连接,每一个请求的响应时间平均为1ms,最大不超过2ms。
3)用户不会明确地提出,但是他们认为:
网站就应该有流畅的反应、他们的在线交易就应该是安全的、他们的隐私就应该受到保护。
因此,开发者需要参考类似应用和相关标准,去捕获用户的隐式需求。
4)应该尽早地获取用户对人机交互的反馈。
有人用HTML或电子幻灯片向用户展示界面;这种方法通常难以体现用户与软件的交互过程。
也有人构建原型系统,供用户试用;这种方法需要较长的准备时间,可能不够快速。
我倾向于用白纸、即时贴、画笔等构建低技术的软件界面,然后让用户试用该界面去完成一个特定的目标。
在这个过程中,我充当界面的后台系统,根据用户的输入,对界面做出修改,以模拟软件响应。
这种方法成本低、速度快,是交互设计的好帮手。
5)敏捷开发通常以3~6周为单位进行迭代。
在每一个迭代之后,都应该将软件发布给用户,或者邀请用户来试用软件。
根据用户的反馈来规划下一个周期的开发任务。
6)敏捷开发将“现场客户”作为一条实践方法。
这看上去很困难,实际上未必。
例如,可以邀请客户定期来访,或定期拜访用户,以获取用户反馈。
有些团队成员可能有相关的业务背景,他们可以充当现场客户的角色。
总体而言,这是一个思维方式的问题。
有些软件开发者以技术为中心、以自我感觉为中心,不太在意用户的目标和感受。
即便提供了用户的迫切要求,他们也可以用种种借口搪塞过去——这就是AlanCooper所谓的“技术人”的特点。
只有真正以客户为中心,以上方法才有可能获得成效。
10、你去年在测试和质量保证方面学到了哪些东西?
在过去的一年,我从理论和实践上都有所收获。
在理论上,我阅读了多本软件测试与质量方面的书籍。
给我留下深刻映像的是:
●《AgileTesting》提出的测试四象限。
它利用technology-facing,business-facing,supportteam,critiqueproduct这四个维度,对测试进行了分类。
这是一种新的测试思考方法,对于测试活动的选择很有帮助。
●《持续集成》提出的持续地交付价值流的观点。
根据一年来在rollingtest中的实践,我切实地体会到它的重要性。
●《敏捷软件开发》提出的敏捷思维集合(mindset)、原则和方法。
这是一本需要反复阅读并实践的好书。
●《xUnitPatterns》提出的测试模式和重构方法。
目前,我在工作中有意识地应用这些模式,获得了不错的效果。
●公司提供的UserExperienceDesign的培训。
它让我认识到人机交互和低技术原型的重要性
在实践上,我也有一些积累。
●实践了类似于持续集成的rollingtest。
●学习并在工作中应用了IronPython。
利用IronPython完成了大部分的测试自动化工作,从而较好地掌握了Python语言,并认识到动态语言对于提高测试效率的积极意义。
●负责系统的性能和压力测试,并在这一过程中学习了《Web应用程序性能测试指南》。
问题4的回答表明我的工作还有许多不足,需要进一步改进。
●在工作中积极反思,总结了一种探索式的自动化测试开发方法。
后记:
花了许多时间写出这些回答。
一些概念性的问题(如问题1,5,6,7)看似简单,但是若要给出准确的答案,也颇费脑力。
这也许是“命题作文”的好处吧:
强迫你思考一些自以为熟悉的概念,从而获得新的认识。
在回答的过程中,平日的积累发挥了重要的作用。
此外,以上所有问题都是开放式的。
在真实的面试过程中,面试官会根据答案做进一步的刺探(prod)。
一问一答的过程将激荡出更多的灵感,此间精彩非答卷式的文字可以比拟。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 测试 经验 经典