测试用例的一些“真相”与“事实”
测试用例存在一些真相与事实,有些广为人知,有些却很隐蔽。正是基于这些真相与事实,可以对我们的手工测试、自动化测试、甚至规模化的自动化测试(数以万计的用例)带来不同的启发。
测试领域有一个几乎是共识的结论,我们不能完全测试(Complete Test)。除了这个结论本身,其原因也有很大的参考价值:
软件系统本质也是一系统,是由一层层依赖组成的,当我们想测某一点时,总会有假设,但是这个假设本身有时也需要另外的用例来覆盖。而哪一层的假设是可靠、不需要再测试的判断往往是凭个人经验决定的,也许离真实很远。
瀑布流程中的测试用例规划通常是基于规格的,较少考虑“人”的因素。现在我们已经知道,针对同一个需求,由不同的开发实现后质量是不一样的,从而用来验证和评估软件的质量的用例集也应该是不一样的,这也是探索性测试的切入点之一。
客观规律是,对事物的认知需要一个过程,也就是哪怕将测试问题锁定在某一个层次、用例大小确定在某一粒度,我们也不一定能一次性确定需要的用例集。曾经有一个尝试,让不同的测试专家对三角形测试问题给出用例(即给出的a、b、c的长度能否组成三角形),实际结果是得到的用例数从几十到上百不等。
给我们的启示就是测试设计基本不可能“一劳永逸”,我们需要时刻对待测系统出错的方式保持警惕、以及针对性的测试设计。
想象你给某片树林拍了一张照片,聚焦在其中的某一颗树。现在请回忆并描述你的照片,大概率你会比较准确地刻画出那颗树的形态,同时也会大概率不知道它的旁边还有多少其它树、树下的草地的颜色、是否有鸟等信息。同样地,我们在设计用例时,通常也是在关注其中一个测试点的同时,就也会忽略其它一些信息。有时即使想“面面俱到”地测试这整个风景,但信息量的膨胀也会使我们妥协、甚至放弃,导致最终写下来的用例只是设想的一部分,即那一顆树。一个用例能记录下来的信息量通常并不能反映测试设计人员脑海中全部的信息,信息丢失在此不可避免。
如果细分,用例其实有两种形态:抽象用例、实例化用例。简单来讲,抽象用例是以抽象的方式描述了前提条件、步骤与结果,里面涉及的测试数据比如用户都只是一个抽象表达,暂时与某测试环境没有关联。实例化用例则是在执行时,用例里面的数据与场景与实际的测试环境发生了关联,比如用户的id将不再是抽象表达,而是实实在在的系统存在值,比如某UUID值。
根据经验,抽象用例并不一定能转化为实例化用例,业务可能的变化、真实测试环境的限制等都可能是导致转化失败的因素。从而可能的后果就是实际执行的用例、用例集与设计时的用例、覆盖期望存在差距。
更进一步,如果用例设计人员与执行人员不是同一人会发生些什么呢?根据上面的真相2、真相3的讨论,信息的丢失会一定程度地存在着。但是比这个问题更严重的是,设计人员认为他已经完成针对问题的测试分析、设计工作,理论上的覆盖率已经达到。同时设计人员不参与后续的执行工作,则对软件可能的变化将得不到及时的反馈,时间一长,设计的用例集也许和产品需要的用例集相差会很大。另一方面,对执行人员来说,因为有一个用例集存在,他们的第一要务是完整地执行它们。过去的实践表明,专注于执行的测试人员评估自己的工作是以已执行完成的用例百分比来衡量的。他们对当前用例集是否是合适的,是否需要根据实际情况在某方面增加新的用例等思考却相对较少。
从而我们看到,如果用例设计人员、执行人员分开,由于他们评估自己的工作的方式的不同特点,将导致的一种可能状况就是,他们对各自的工作都比较满意,可是最终映射到产品、系统的测试效果并不理想。
我们可以说用例本身只是一个计划、一个意图,只有基于它与待测系统真正交互后才算完成一个测试闭环。周期越长,反馈环就越长,从而单位时间内通过用例得到的信息就可能更少。从上面的讨论我们还看到,用例从在脑海中构思、到记录、到实际执行,存在着信息损失。尽可能地加快整个测试环,才能一程度弥补用例生命周期中各环节的信息损失。
开发、测试团队通常基于BA整理的需求和场景分析、设计用例是一个很自然的选择。QA会实施各类分析技术得到用例集,但这通常也是基于BA的业务输入和基于开发的技术选型,实践上会存在认识偏差和识别用例优先级排序困难等。而这恰恰是客户用例、场景输入的有价值的地方,尤其一些场景是客户自身的“痛的领悟”。对一个已经生产运维的系统,客户测试团队的用例集、甚至运维团队的缺陷集都是高价值输入。
在真相3里我们提到抽象用例和实例化用例,以及一个用例所包含的基本元素:前提条件、测试步骤、期望结果。比如一个场景,用户搜索产品、添加到购物车、提交订单、取消订单,如果一个用例关注在订单的取消,其前提条件是一个已提交的订单待取消。那么问题来了,这个已提交的订单是怎么来的呢?在写用例时我们可以以几秒钟的代价写下“前提:系统存在已提交的订单待取消”。可是到了实际执行时,尤其是手工执行,也许这个取消测试步骤本身只需要1分钟,但是这个前提却需要10分钟,即前提的准备代价数倍于当前用例的主要执行点 。
给了我们什么启示呢?主要是提醒测试人员,请注意“想得到却做不到”的陷阱。虽然在设计用例时,我们可以基于逻辑写下这个用例,但却不一定真的能测它或者测试它的代价非常大。尤其是一些负向的测试,比如网络状态模拟等。同时这个现实给了我们另外的思考契机,就是怎么去平衡这个代价。考虑上面的订单例子,前提条件至少有以下几种方式得到满足:
用SQL的方式向数据库插入(但数据不一定合法)
生产环境数据脱敏后Copy至测试环境,从中寻找合适的订单
直接通过UI,按照用户真实的流程生成订单并提交(合法但效率不高)
通过API的方式,调用对应的API生成订单并提交
显然,它们各有优缺点,我们需要考虑它的效率、以及对真实业务过程的还原程度。
现在我们提到E2E(End to End)一般认为它是与用户验收测试(UAT)相关,即去覆盖真实用户有用户场景从头到尾。UAT与系统测试(ST)的区别是什么呢?以上文中的订单场景为为例,ST上下文的取消订单不会关心这个订单是从哪来的,但是UAT场景中确要确保这个订单是之前的用例通过用户合法操作得到的。ST和UAT的不同是多方面的,恰恰这一点较少被关注到。既然ST的订单可以通过合法操作得到,为什么还要用不那么合法的SQL方式直接插入呢?这其实是用例执行时的效率问题,一般认为UI的方式慢且不稳定。换言之,这是一个妥协,如果我们有更好的方式在保证操作合法的同时又能确保效率,我们就不需要这种妥协。由此,从逻辑层面,我们可以得到这样一个结论,一个用例循着前提条件,就能“追溯”到当前场景的起点,用例尤其是业务用例的本质都是E2E的(第一个E是场景的起点,第2个E是当前的测试点)。
正确认识这个E2E真相、以及承认用SQL这样非业务方式直接写入是一种执行层面“妥协”非常重要,它会让我们重新思考当前的一些“理所当然”的实践,其出发点、收益、风险,从而改进与提高。
我们一般都会认同回归测试很重要,但要真正做好回归测试并不容易。回归什么、想达到的目的是什么、效率怎样都是回归过程中需要认真对待的问题。
在瀑布的流程中,回归测试一般是以一个单独的测试阶段而存在的。它有自己的资源分配、准入、准出。迭代开发中,至少在实践Scrum的团队中,没有明确有回归这么一个阶段存在。引出的问题就是我们应该 在敏捷中怎样来对待回归测试呢,它是一个阶段、还是一个类型?笔者观察到的敏捷团队的实践中,甚至会出现两个极端的现象。一方面,有团队对有分层次的测试策略,完备的自动化测试,使得一次回归的代价很很低,回归集在持续维护。在重构、新功能引入等情况下,回归可以随时执行,在几分钟内或不超过1小时就完成一次回归,回归以一种测试类型存在而非一个测试阶段。另一方面,也有团队在重构、新功能引入等情况下对回归这件事就“随缘”了,没有回归目标,回归测试是以天为单位的代价存在,且是在一个相对其它开发、测试活动之外一个明显存在的阶段。
从用例的角度看,回归用例集一定程度是代表“回归什么”。没有这个基础,回归测试将是一种无序的、难以评估的方式进行。持续保持回归用例集有效,添加新用例、剔除无效用例等用例集的维护工作很重要,尤其是探索性测试过程中产生的有价值的新用例怎么加入回归用例集。
貌似笔者在上面描述了很多与用例相关的不确定性、甚至不可能性,其实想传递的主要信息就是:
对待测试不要盲目乐观和轻视这些问题
要正视这些问题,同时看到在解决这些问题时带来的好处
以此为基础,我们就能进一步思考诸如以下一些问题:
为什么尝试以手工用例设计的思路设计自动化用例时,自动化努力通常以失败告终?
为什么自动化测试用例在膨胀的同时,测试对系统质量的反馈能力反而难以评估了?
- 相关阅读 -
点击【阅读原文】可至洞见网站查看原文&绿色字体部分的相关链接。
本文版权属ThoughtWorks公司所有,如需转载请在后台留言联系。