当谈论覆盖率时我们在谈什么?

80-20-Rule

代码覆盖率 vs. 测试覆盖率

代码覆盖率通常指跑完测试后, 由工具自动统计的在跑测试的过程中被测代码的覆盖率, 细分的话包括语句覆盖率, 分支覆盖率, 函数覆盖率等. 由于代码覆盖率可由工具自动产生, 采集成本非常低, 而又比较直观, 所以历来受到开发团队及管理者的欢迎, 有的组织甚至将其作为 KPI 指标之一.

然而围绕着代码覆盖率, 有很多有趣的事情, 尤其是将其作为 KPI 的时候. 你会发现, 长期在低位徘徊的代码覆盖率, 突然之间会有一个比较大的提升. 究其原因, 是开发团队在短时间内加了”测试”. “测试”是打引号的, 因为当我们近距离观察这些”测试”的时候, 会发现通常是调用了某个高层的入口函数, 因而牵出很多底层函数, 覆盖率就上去了, 然而, 没有一个断言(assertion), 或者是区区几个断言. 也就是说, 把产品跑了一遍, 但没有判断其行为是否符合预期, 而代码覆盖率突然就达标了.

尽管对于追求自我改进的团队来说, 不会这么掩耳盗铃, 代码覆盖率依然是有价值的反馈指标, 但这从侧面说明了代码覆盖率并没有表达出我们对于外部质量真正的关注点. 那么我们对于质量真正的关注点是什么呢?

是断言的覆盖率, 即测试覆盖率. 换句话说, 我们真正关心的是, 我们总共应该有多少测试用例/验收条件/检查点, 它们中有多少已经被覆盖了, 即做出了真正的断言. 但目前为止, 还没有工具能自动统计跑完测试后, 测试覆盖率是多少. 代码覆盖率仅仅是无法自动统计测试覆盖率时的一个替代品.

为了统计测试覆盖率, 需要准备分子和分母的信息. 分母是产品”完整”的测试用例列表, 分子是已经执行的测试用例列表, 包括手工和自动. 如果你关心测试覆盖率, 而手头又没有这两个东西, 就要开始准备了.

注1: 利用现有的 xUnit 测试框架, 可以在某种程度上得到测试覆盖率. 比如可以将”完整”的测试用例列表用 xUnit 的测试用例表达出来, 其中对于还没实现的, 设置为 ignore. 这样可以从最后的报告中看出总数, 和 ignore 的数量(当然如果你不做断言, 还是白搭). 现在更多的是借助管理工具甚至 Excel, 来手工维护”完整”的测试用例列表及状态. 如果你知道有更好的方式, 请告诉我.

注2: 前面”完整”的测试用例列表, “完整”一直打着引号, 因为这是一个无法证明的问题, 我们只能根据经验设计测试用例, 无法保证其完整性, 并且随着产品的开发, 这个列表也会动态更新. 至于如何让测试用例尽可能完整, 是组织应该投入的地方.

此测试覆盖率 vs. 彼测试覆盖率

基于前面的描述, 那么当我的测试覆盖率达到某个比较高的数值, 比如80%, 是不是我就可以比更低的数值比如20%, 对产品更有信心呢? 答案取决于你的测试用例的设计.

我们都听过80/20原则. 比如用户80%的时间在使用20%的功能, 20%的功能就可以支撑起用户最关键的业务场景. 那么, 如果80%的测试覆盖率, 覆盖的是那不常用的80%的功能, 而20%的覆盖率, 覆盖的恰恰是最常用最关键的那20%的功能, 那么, 你是否还像开始那样, 相信80%的覆盖率带来的安全感呢?

基于测试覆盖率很难达到100%这个前提, 基于我们的发布时间总是很紧张而又要保证质量这个前提, 我们必须投入精力, 做测试用例的价值分析, 挑选出最有价值的测试用例, 优先安排资源实现和运行.

如果团队的测试用例没有经过价值分析, 没有优先级划分, 那么这就是接下来马上应该做的事. 这牵扯到一个问题, 测试人员及测试技能的价值.

当我们谈论测试技能时我们在谈什么

最近几年随着自动化测试框架的流行, 评价一个人员测试能力的标准逐渐变成了是否能写自动化测试. 如果照这个标准, 所有的开发人员一夜之间都具备了合格的测试能力. 这显然是一个不成立的结论.

测试至少分测试用例的设计和测试用例的编写执行两部分. 自动化测试的长处仅仅在于编写执行. 使用自动化测试框架并不会自动让我们的测试更有效, 更完备, 更具洞察力. 而测试的有效性和完备性, 通常是我们更关注的. 然而遗憾的是, 通常组织中这方面的知识比较欠缺, 关注度不够, 技能交流较少.

如果我们交流测试知识时, 更多的是谈论 xUnit, RobotFramework等, 而不是等价类/边界值, 恶邻测试法/快递测试法, 关键路径分析等, 那几乎可以肯定我们遗漏了更重要的东西.

要在时间资源人力资源有限的情况下保证产品质量, 我们需要提高测试用例的设计能力, 价值分析能力, 安排合理的测试策略.

本文转自:http://liguanglei.name/blogs/2015/06/01/code-coverage-vs-test-coverage/

Share

BDD在移动开发中的应用

bdd

内容转自:文章作者来自:Prateek Baheti&Vishnu Karthik,图片来自网络。

Prateek Baheti是ThoughtWorks的开发人员,他做了两年Twist的开发。除了写代码,他还喜欢驾驶,听音乐,看板球比赛,打乒乓球。

Vishnu Karthik是ThoughtWorks的开发人员,他一直从事Twist的开发和测试自动化。之前他在比哈尔(印度东北部一座城市)的医疗保健服务中心工作。除了写代码,他还喜欢玩极限飞盘。

移动应用程序现在已经非常普及,大多数的应用可以支持3种主流平台:iOS、Android和Windows phones。此外Firefox OS平台的市场占有率也在不断提升中。

应用程序的功能是与平台无关的。但是不同的平台还是会有差异,例如处理消息事件的方式等。测试移动应用程序,并保证它们能在所有的平台上正常工作,是一项很有挑战的工作。我们需要为不同的平台编写不同的测试用例并且分别执行。这样就会导致各个平台上的测试覆盖率不一样,而且测试用例也会变得原来越难维护。平台级别的差异实际上和应用程序的功能是无关的,所以理想的中的测试用例应该纯粹使用业务语言进行描述。

行为驱动开发(BDD)风格的测试可以极大地改善这种情况。

为什么使用BDD?

BDD风格的测试用例使用纯业务领域的语言进行描述。这种方式提供了一种更好的理解测试用例的途径。测试用例使用了高层次的逻辑表述,而不会包含具体的实现细节(例如点击一个按钮)。

BDD方法有很多工具的支持,这些工具可以把测试案例规范和底层实现细节关联起来。这种风格的测试已经被证明是易于维护的,也易于描述测试用例。

针对移动应用程序,BDD可以在以下方面提供帮助:

1. 对底层细节进行抽象并提供高层次的步骤(steps):

BDD对底层细节进行抽象,并提供高层次的测试用例步骤,这样就会与平台无关了。下面来看一个发短信的应用程序的测试场景。这个测试使用Twist编写而成。

上面的测试不涉及任何的底层实现,而是使用了平台无关的语言,用业务术语描述测试场景。在这个测试用例中,接收消息提示是一个业务上的术语,对它的实现将会针对平台而不同。

2. 因此这种测试用例可以被不同平台和团队使用:

会有一个通用的接口来负责和不同的实现进行交互。

public interface MessagingDriver {
  void login(String userName);
  void sendMessage(String toAddress, String message);
  void checkForNotifications(String address);
}

现在每个平台可以有特定的实现了:

public class AndroidMessagingDriverimplements MessagingDriver {
  public void login(String userName) {
  // login for android
  }
  public void sendMessage(String toAddress,String message) {
  // send message for android
  }
  public void checkForNotifications(Stringaddress) {
  // check for notification for android
  }
}

可以使用环境变量在运行时切换到不同的平台实现上。

public class MessagingDriverFactory {
  public static MessagingDrivergetMessagingDriver() {
    String testPlatform =System.getenv("TEST_PLATFORM");
    if (testPlatform.equals("android")) {
      return new AndroidMessagingDriver();
    } else if (testPlatform.equals("ios")) {
      return new IOSMessagingDriver();
    }
    throw new RuntimeException("Failed to find animplementation");
  }
}

这样,测试实现仅仅需要去使用工厂模式和事先设置好的合适的环境变量,测试运行的时候会自动匹配相对应的平台驱动。

测试数据和测试场景不再需要重复,它们被视作一个可以执行的文档,从而共享给同一个应用的不同平台的团队

Share

我和敏捷团队的五个约定

我——作为一名测试人员——有一个与众不同的习惯:每当要加入一个新项目的时候,我总会找到项目中的同伴,真诚而亲切地说:“为了更好地合作,我有5个约定,希望大家能尽量遵守”。

约定1. 业务分析师们,我们其实是同一个角色的两种面孔,请叫上我们参加客户需求会议

我们的团队需要让客户频繁的得到可用的软件,客户的不断反馈会给软件的未来做出最正确的方向指引。

如果我们交付的软件有很多质量的问题,存在大量的缺陷,客户会被这些缺陷的奇怪行为干扰,没有办法把注意力放在软件本身的价值是否符合他们的真正需求上, 不能给出最有价值的反馈。所以,我们只有频繁的做测试,在每次交付之前都把质量问题找出来告诉我们的团队,问题才能及时的得到改正。

而我坚信“prevention is better than cure”(预防胜于治疗),我会要把工作的重点放在预防缺陷上,这样可以节省Dev们很多修复缺陷的时间与精力。

为了达到这个目的,我需要跟你一起参加客户需求会议,尽早的了解客户需求与使用软件的惯常行为。那么在你完成需求的验收条件的定义的时候,我也基本完成了测试用例的准备。

我们可以赶在开发人员们写代码之前就告诉他们我要测什么,让他们减少因为过于乐观而漏掉的一些重要的有破坏性的情况,减少缺陷的发生。这是我测试的一项重要任务。

如果你们在大部分需求都整理好了再交给我们,我会浪费掉等待的时间。更重要的是,开发好的软件里面已经有很多本来可以不存在的缺陷在里面了,开发人员们可能需要加班加点来保证在项目最终交付时间之前把它们改好。这样很容易产生新的缺陷的。

所以,请让我尽早了解需求,请不要让我到项目后期才能开始测试。

约定2. 开发人员们,虽然你们是编写自动化测试的专家,但请听听我们意见

我知道,对于你们,自动化测试不过是利用junit, rspec, selenium,watir,uiautomation等等写出的“另一段程序”而已。而对于80%的QA来说,编写自动化测试并不是一件简单的事情。

不过我仍然相信,有测试人员介入的自动化测试更有价值。

你们用单元测试,集成测试来保证代码的质量。然而你们的这些日常测试离代码更近,离最终用户还点远。很多测试都不是在测软件功能。

你们可以把功能测试写的又快又多,而我们可以指出什么功能测试最有必要加到自动化测试中。

你们平时大部分精力都在编码上,没有太多时间去查都有什么缺陷。而我们可以指出什么地方缺陷可能会出现的比较频繁,建议在这些脆弱的地方加自动化测试。

所以请听听我们的意见,我们可以给你们提供这些信息。

约定3. 项目经理们,请不要要求我们测试软件的所有路径

软件测试是一个永无止尽的任务。基本上没有什么软件简单到我们能够尝试完它的每一个可能的路径的。就连一个看似简单的微软计算器都有无穷尽的路径,无止尽的输入,更何况比这个更复杂的商用软件。

如果你们担心没有尝试过全部的路径不可靠,疑惑我们怎么敢说这个软件质量是好的还是坏,都有什么风险。请你们先注意,我们是跟业务分析师一样,都了解软件的价值的。价值可以帮我们做出判断,什么时候可以停止测试并对客户说我们的软件已经满足您的要求了,请放心使用。

因为我们了解价值,我们可以肯定的说哪些软件的使用方式是至关重要的,哪些是不太可能出现的。我们会在全面测试了软件以后,把主要精力放在价值高的功能点上。合理的利用项目有限的时间。

因为我们了解价值,我们可以正确的把发现的问题分类。我们可以帮助dev们把精力放在重要的缺陷上,避免把时间放在对于客户微不足道却不得不花费大量精力才能修正的问题上。

所以,请不要要求我们无止尽的测试一个软件。我们了解价值,请相信我们的判断。

约定4. 迭代经理们,如果对于交付风险有任何疑问,请来询问我

BA和Dev们都是关注一个软件在什么情况是可以良好的工作。而我们除了验证这些情况以外,大量的时候都用在寻找什么样的情况软件不能正常的运行。所以除 了针对定义好的软件行为进行测试,我们还会做很多探索性测试。我们通常可以通过这样的测试发现一些没有定义的、不曾预期的行为。这些行为往往将会构成软件 交付的风险。

我们会告诉你们现在都发生了什么问题,分别分布在哪里。

我们会告诉你们,在什么情况下软件可能会有异常行为,是不是会牵连到其他的部分,是否可以绕过去。

我们会告诉你们,哪些部分功能比较不稳定,需要更多的留意。

约定5. 测试人员们,那些敏捷实践对于我们也是有用的。

结对不是dev们的专利。我不希望总见到你们独自坐在自己的位置上冥思苦想。走出去,跟其他队友多多交流!

多跟测试队友交流,pair看看设计的测试用例是不是够全面,独自一个人想到的未必足够好。他们会给你诚恳的意见的。对他们,也请一样认真对待。

如果你发现开发人员们做出的架构决定使测试工作变得更困难。那么请大声地告诉他们,design for testability(提高你们设计的可测性)。

如果你发现业务分析师写的需求无法验证,定义的客户行为不够具体,一个用户故事中包含太多了功能点,等等,那么也请大声地告诉他,INVEST(独立,可协商,价值,可估算,短小,可测)。

也请你们多跟开发人员结对写自动化测试,既可以帮助你们学习怎样更好的编写自动化测试,也能帮助开发人员们结对更多的了解用户行为。

这就是我的五个约定,它们是我在团队中顺利展开工作的基础。


作者:覃其慧,ThoughtWorks敏捷咨询师。她参与了大量的敏捷软件开发的实践和敏捷咨询。目前主要关注以价值为驱动的敏捷测试。


本文原文发表于InfoQ:http://www.infoq.com/cn/articles/thoughtworks-practice-parti

Share

敏捷测试之借力DSL

随着敏捷越来越广为人知,敏捷测试也更多受到了大家的关注。在这里,我想谈一下我在敏捷项目中遇到的一个自动化测试相关问题以及我们如何借助DSL领域专用语言来解决它。

对敏捷软件开发方法有一定了解的人都知道,敏捷软件开发过程是一个迭代式交付的过程。每个迭代相当于比较小型的交付周期。那么,为了配合频繁的软件交付,敏捷测试相对于传统测试必须要做相应的调整。这也导致了敏捷项目中的测试面临几个特有的挑战:

  1. 频繁的回归测试以确保每个迭代的成果都是可交付的
  2. 让整个开发团队参与到测试活动中以缩短质量信息的反馈周期
  3. 让客户参与到测试活动中来帮助提高测试的有效性

自动化测试在应对频繁的回归测试这个挑战上起着非常关键的作用。自动化测试做不好,团队最终会被每个迭代都会增加的回归测试工作量压垮。我经历过的一个团队,在这个团队中,大家很早就意识到了自动化测试的重要性,在自动化测试上的投入不遗余力。我们相信自动化功能测试增加到足够多的时候,它就能指导手动回归测试,保证整个交付过程顺利进行。

的确,自动化测试刚开始进行的时候,我们收益颇多。每增加一个自动化测试,我们就能减少一些手动测试。自动化测试让我们我们有比较充裕的时间来手动测试那些还没有来得及自动化的、难以被自动化的功能点上,而且还能有时间和精力做探索性测试。这个结果让团队感到生活很美好,也让我们对自动化测试坚信不疑。

然而好景不长,随着自动化测试的不断增加,我们会面临这样一些问题:

  1. 自动化测试是围绕着实现细节展开的。随着数量的增多,业务的轮廓很容易迷失在细节中。
  2. 在功能级别丧失了对测试的追踪。由于测试人员无法具体知晓那些测试案例被自动化测试覆盖。每次回归的时候,团队都需要回归整个测试组。

于是,我们的手动测试越来越难得到自动化测试的帮助。它开始成了项目的鸡肋。测试代码阅读困难、维护困难以及测试结果的看起来也很费劲。这直接导致了我们不仅要投入相当的时间来增加自动化测试,也要投入不少时间来阅读并利用测试结果。

于是我们开始重新审视自动化测试的做法,继续摸索更好的方式。

很快,我们发现“能够跑起来”并不是好的自动化测试仅需的特性。让我们通过一段测试代码来看一下具体怎么回事。

selenium.open(“/”)
selenium.type(“id=username”, “myname”)
selenium.type(“id=password”, “mypassword”)
selenium.click(“id=btnLogin”)
selenium.waitForPageToLoad(30000)
assertTrue(selenium.isTextPresent(“Welcome to our website!”))

这个测试中,我们首先打开了一个页面,在页面中寻找一个id为username的输入框,输入“myname”,然后再寻找一个id为password的输入框,输入“password”,然后点击一个id为btnLogin的按钮,等待30秒以后,断言页面应该出现的文字。

我们可以看到,这个测试的实现很完整的描述了测试的操作过程,是一个面向步骤而不是目的的描述。当然,稍加分析,我们也可以看出来这个测试的目的是测用户登录成功系统。

但是,想象当我们有很多这样面向步骤来描述的测试时,要从中抽离出被无数细碎的操作步骤所淹没的测试意图,并把测试的结果利用起来,其实并没有那么直观。而且,如果在测试中出现了错误,对于问题的具体功能点的定位也不是那么容易。

与此同时,并不是团队中所有的成员都有能力阅读和编写这样的测试。这无疑降低了团队成员对于自动化测试的参与度。对于客户,自动化测试更是一个黑盒子,做了什么,没做什么,基本上搞不清,更谈不上参与到自动化测试中,帮助提高测试的有效性。

种种状况,究其原因就是测试可读性太差,测试意图不够明显。可运行并且容易读的测试才是好的自动化测试。这样才能够保证任何时候,我们不会丧失对于测试案例的跟踪与管理。测试人员随时都可以通过快速阅读测试,了解那些功能已经被自动化测试覆盖,有效规划手工测试的工作量。

怎么提高测试的可读性呢?

我们的解决办法是DSL领域专用语言。

什么是领域专用语言?在马丁大叔的博客里有比较详细的描述。大致来说,领域专用语言就是针对某个领域的特定目的编程语言。不像Java、C#等通用语言,可以解决任何领域的问题。领域专用语言通过自己独特的语法结构来描述更接近于专业领域语言的业务。

让测试的描述能够接近被测系统的领域语言、使测试意图得到清晰表达就是我们想要得到的效果。DSL正好能够帮我们实现。

让我们再看看之前的那段代码:

selenium.open(“/”)
selenium.type(“id=username”, “myname”)
selenium.type(“id=password”, “mypassword”)
selenium.click(“id=btnLogin”)
selenium.waitForPageToLoad(30000)
assertTrue(selenium.isTextPresent(“Welcome to our website!”))

由于使用的是通用语言,在我们这个特定的使用场景中显得过于细节化、过程化,不能清晰表达测试意图。

换成DSL,我们的测试就可以直接用验收标准的语言来描述如下:

Given I am on login page
When I provide username and password
Then I can enter the system

这样测试的内容就直观多了,还包含了一些业务信息,让我们知道这个是在测试一个登录的场景,而不是任意的输入信息,兼顾传递了业务知识的职责。至于这些DSL背后能够运行的代码,也被隐藏起来。如果是不能够阅读原来那样的测试代码的人(不管是需求分析人员还是客户甚至一些对自动化代码关注比较少的测试人员)想要加入到自动化测试活动中进行反馈,就不会被DSL背后的代码带来的“噪音”所影响。

当然,在我们的现实应用场景中,这个需求没有那么简单,我们的验收标准还会考虑不同的数据比如输入不同组合的用户名密码:

Given I am on login page
When I provide ‘david’ and ‘davidpassword’
Then I can enter the system
Given I am on login page
When I provide ‘kate’ and ‘kate_p@ssword’
Then I can enter the system

以及更多的测试数据。

那么这种情况下,仅仅是比较通俗的语言还是不够的,毕竟测试数量在那摆着。如果测试数量不能减少,维护起来仍然很麻烦。打个比方,如果系统的实现变成了每次都要输入用户名、密码和一个随机验证码,我们就需要在我们的自动化测试中修改多处,比较繁琐。因此,我们需要在可读性比较好的自然语言描述的测试上,把它的抽象层次再提高一点。

幸运的是,我们当时选择的DSL工具是cucumber,它除了提供了几个测试的描述层次:Feature,Scenario,Steps,还提供了非常好的一种组织方式—数据表。

这样,我们的这个自动化测试就可以把之前的那个登录的功能根据特性、场景总结和具体的步骤分离开来,清晰的分层,同时利用数据表我们的测试精简成一系列被重复多次但输入数据有所变化的操作过程,如下:

Feature: authentication
In order to have personalized information
I want to access my account by providing authentication information
So that the system can know who I am
Scenario Outline: login successfully
Given I am on login page
When I provide ‘<username>’ and ‘<password>’
Then I can enter the system
Examples:
|username |password |
|david |davidpass |
|kate |kate_p@ssword|

测试这下看起来就更清爽了。首先,用Feature关键字,我们把测试分类到login这个大特性下的,并对这个特性本身的业务目的进行相关描述,带进业务目标,传递业务知识;然后用Scenario关键字来提高挈领的标明我们这个测试场景中做的是测试登录成功的情况,并且把步骤都写出来;最后,我们用Examples关键字引出具体的数据表格把用到的数据都展示出来,避免我们的相同步骤因为测试数据的变化而重复若干遍造成冗余。万一碰上了需求的变化,要求同时提供用户名、密码和验证码,那我们的测试也只需要改动较少的地方就足够了。

更棒的是,用了这种数据表的方式,整个团队的协作效率提高了。对于写代码没有那么顺畅的测试人员来说,增加自动化测试也就是增加更多测试数据,填充到数据表里就可以了。

就这样,我们用DSL实现了可执行的可读性高的文档。帮助了回归测试,降低了文档维护难度,也促进团队成员利用测试来传递知识的积极性,让更多人能够参与到测试中。如果您的团队也遇到了类似的问题,不妨也尝试一下。


本文原文发表于InfoQ:http://www.infoq.com/cn/articles/leveraging-dsl-in-agile-test

Share