我们的测试为什么不够敏捷?

jopen 12年前

        测试是为了保证软件的质量,敏捷测试关键是保证可以持续、及时的对软件质量情况进行全面的反馈。由于在敏捷开发过程中每个迭代都会增加功能、修复缺陷或重构代码,所以在完成当前迭代新增特性测试工作的同时,还要通过回归测试来保证历史功能不受影响。为此我们期望:

        测试范围足够广:

  • 测试用例要覆盖所有功能;
  • 要在各种可能的环境下作兼容性测试;
  • 系统的稳定性、性能都要测试;

        测试频率足够高:

  • 每日构建产生的版本要保证可用;
  • 每个迭代都需要对历史功能做回归测试;
  • 释放前或上线后如果打了补丁,就需要回归;

        但实际情况往往不遂人愿:

        实际测试周期变短:

  • 上线时间提前确定,研发进度延期,测试计划被迫延后;
  • 最后阶段经常会临时增加测试任务;
  • 所有人都知道还需要再经过一轮测试,但时间没有了;

        有效测试资源稀缺:

  • 临时从其他项目抽调的测试人员不能立刻有效的开展测试工作;
  • “搞不清楚”本项目的研发人员到底是不会做测试还是不愿做测试;

        因此由于客观上的资源和时间限制,完整的、及时回归测试在人工测试情况下,往往是不可能完成的任务。团队内部也会产生各种争执:

  • 提交给测试的版本为什么研发人员不先做通过性测试?
  • 这次代码改动量不大,有必要再花那么多时间回归么?
  • 当初不是承诺这次修改肯定不会影响以前的功能么?
  • 怎么不早说要支持 Chrome 浏览器,现在哪还有时间测试啊?
  • 怎么能让现场出现这种低级的 Bug,打补丁后为什么不仔细回归一遍?

        争执越演越烈,最终有团队成员爆发了:“这简直不是人干的活!”。

        您怎么看待这句话呢?

        其实话糙理不糙,用更理性的语言翻译一下就是“有些工作不应该以人工的方式来完成”,比如:

  • 大量机械的、重复性的回归测试;
  • 结果的正确性不依赖主观判断的测试;
  • 需要模拟大量数据、大量并发量的测试;
  • 需要不间断执行的测试(比如7*24 小时持续执行);
  • 需要短时间内完成的大量测试用例执行(比如完整的功能回归测试);

        通过自动化测试可以极大的提升回归测试、稳定性测试以及兼容性测试的工作效率,在保障产品质量和持续构建等方面起到举足轻重的作用。特别是在敏捷开发模式下,自动化测试是必不可少的。

        目前业界的商业/开源自动化测试工具有很多,比如,功能测试工具有 QTP、Selenium 等,性能测试有 LoadRunner、JMeter 等。其工作原理无非都是通过“测试脚本”和“测试数据”来完成“测试过程”,并比较“测试结果”,进而形成“测试报告”。

        本文不对这些测试工具的差异或优劣进行对比,只以作者目前采用的 Selenium 为例进行分析:敏捷开发模式需要自动化测试,但自动化测试本身的敏捷性又如何呢?

        Selenium 是针对 Web 应用的开源自动化测试工具,通过编写模拟用户操作的脚本,它会打开浏览器对 Web 应用进行黑盒测试。可以方便的用于功能测试、兼容性测试、 稳定性测试及并发测试。目前已被主流浏览器厂商广泛支持,同时也是很多其它自动化测试工具(比如,RobotFramework)的底层核心技术。Selenium 由 IDE、Remote Control(简称 RC)、WebDriver、Grid 四个工程组成:

  • Selenium IDE

        是一个用于录制/回放测试脚本的 Firefox 附加组件,录制的脚本可以生成基于 Selenium RC 的测试代码(Java、Ruby、C#等)。适用于快速入门,不太适用于实际较大的测试项目;

  • Selenium RC

        RC 由 Server 和 Client 组成两部分组成,Server 负责加载/关闭浏览器以及作为 HTTP 代理来访问 Web 应用,Clinet 支持多种编程语言和测试框架(TestNG、JUnit、NUnit 等)。

  • Selenium WebDriver

        WebDriver 作为 Selenium2 的核心特性提供比 RC 更简洁易用的 API,是官方推荐的 RC 替代方案。可以更好的支持动态网页,不需要再额外启动一个独立的 Server。

  • Selenium Grid

        是 Selenium 的一个扩展工具,可以很方便地同时在多台机器上和异构环境中并行运行多个 RC 或 WebDriver 用例。

        Selenium RC 是通过在浏览器加载时“注入”JS 函数来操纵后续的浏览器行为,Selenium WebDriver 则通过直接调用各个浏览器内置的本地事件来达到这一目的。WebDriver 目前已经作为 W3C 规范草案,提交由 Google、Mozilla 等浏览器厂商讨论。

WebDriver 规范定义一组与平台、语言无关的接口,包括发现和操作页面上的元素以及控制浏览器行为,主要用于支持 Web 应用的自动化测试。WebDriver 的核心是通过 findElement 方法返回 DOM 对象(WebElement),通过 WebElement 可以对 DOM 对象进行操作(获取属性、触发事件等)。其中 findElement 方法需要的元素定位器(Locator)支持 ID、XPath、CSS、超链接文本等多种方式。

        “WebDriver”顾名思义就是“Web 浏览器驱动”,它专注于解决如何通过外部命令(通常为测试用例)操作浏览器的问题。至于测试用例按照什么顺序执行、执行过程中如何传递数据、测试结果如何断言、如何报告,则可以通过集成其它优秀的专业测试框架(比如,TestNG)来实现(WebDriver 没有必要重复造轮子)。

        下面用以“用户管理”为例,来看看用 WebDriver 实现的“增加”和“删除”测试脚本(只示意部分关键代码)。

        1、在用户列表页面点击“新增”按钮,跳转到新增用户页面:

webDriver.findElement (By.xpath ("//a[contains (@id,'addUserBtn')]//button")) .click ();.

我们的测试为什么不够敏捷?

        脚本解读:

  • By.xpath ()表示通过 XPath 来定位页面元素;
  • click ()表示在找到的当前控件上执行点击操作;

        2、在新增用户页面,输入“帐号”、“密码”、“姓名”,选择“性别”、“生日”和“国籍”,然后点击“保存”按钮,回到用户列表页面,并判断是否增加成功:

1) String account="autotest2";2) webDriver.findElement (By.xpath ("//div[contains (@id,'account_userForm')]//input")) .sendKeys (account);3) webDriver.findElement (By.xpath ("//div[contains (@id,'password_userForm')]//input")) .sendKeys ("1");4) webDriver.findElement (By.xpath ("//div[contains (@id,'name_userForm')]//input")) .sendKeys (account);5) webDriver.findElement (By.xpath ("//div[contains (@id,'sex_userForm')]//input")) .click ();6) webDriver.findElement (By.xpath ("//span[text ()='女']")) .click (); 7) webDriver.findElement (By.xpath ("//div[contains (@id,'birthdate_userForm')]//input")) .click ();8) webDriver.findElement (By.xpath ("//div[contains (@id,'nationality_userForm')]//input")) .click ();9) webDriver.findElement (By.xpath ("//span[text ()='中国']")) .click (); 10) webDriver.findElement (By.xpath ("//a[contains (@id,'userSaveBtn')]//button")) .click (); 11) WebElement ele = webDriver.findElement (By.xpath ("//div[text ()='"+account+"']")); 12) Assert.assertNotNull (ele);

我们的测试为什么不够敏捷?

        脚本解读:

  • sendKeys ()表示在找到的当前控件上输入字符;
  • 2~9 行表示通过输入或点击选择的方式为用户相关属性赋值;
  • 第 10 行表示点击“保存”按钮(点击后会自动转向用户列表页面);
  • 11~12 行表示查找页面上文本内容为新增帐号的 div,并断言该 div 是存在的(不为空);

        3、删除刚刚增加的人员,然后判断是否删除成功:

1) webDriver.findElement (By.xpath ("//a[contains (@id,'deleteUserBtn')]//button")) .click ();2) WebElement ele = webDriver.findElement (By.xpath ("//div[text ()='"+account+"']"));3) Assert.assertNull (ele);

我们的测试为什么不够敏捷?

        脚本解读:

  • 第 1 行表示点击“删除”按钮;
  • 2~3 行表示查找页面上文本内容为新增帐号的 div,并断言该 div 已经不存在了(为空);

        通过上面的脚本就可以实现“用户增加、删除”的自动化测试,并且可以跨浏览器。看到这里您会不会觉得整体还不错,如果测试脚本再能通过录制的方式自动生成就更好了!

        “看”起来确实还不错,但在实际项目中用起来就没那么爽了。这其实是在技术/工具选型时普遍存在的现象:在验证/试用阶段的评价很高,但在投入生产使用时会遇到各种各样的问题,因此大家在选型阶段除了考虑功能,还要考虑技术/工具本身的开放性和可扩展性。

        上面的方案单纯从技术的角度来讲是很不错的:开源、社区活跃、标准化程度高、支持跨浏览器、脚本回放稳定、可集成性高,等等。

        但是如果就这样应用在实际项目中,会从过程的角度暴露一些棘手的问题:

  • 测试脚本是“代码级”的,那么应该由谁来编写呢?

        测试人员和开发人员好像都有编写这些测试脚本义务,但似乎又都有不写的理由。

  • 测试脚本必须不断的修改才能在最新版本上跑通,谁该为此买单?

        在敏捷开发过程中需要快速响应需求的变化(新增、变更),这一点整个团队都好理解。因此如果需求发生变化,开发人员修改代码、测试人员修改测试 脚本,一切顺理成章,大家相安无事。但是在用户需求没有变化时,开发人员频繁修改代码的情况也很常见:比如,修改 Bug、针对“坏味道”做重构、调整页面布局或样式。于是在“毫无征兆”的情况下,测试脚本又无法执行了!

        这时候测试人员可能会质问开发人员:“做之前怎么不想清楚?都已经测试完成的功能,为什么还总是反复修改?什么时候代码才能稳定?”。

        而开发人员此时也会非常理直气壮:“有 Bug 能不改么?页面布局不合理导致用户体验差,能不改么?而且敏捷中的一个重要的实践就是重构啊”。

        大家又是似乎都有道理、也都有苦衷。我虽然作为测试人员,但是在这个问题上还是“偏向于”开发人员的: 在软件生命周期的各个阶段(需求、设计、开发、测试)中,后面的阶段对前置阶段是有一定依赖的,所以越往后期响应变化的难度越大。比如,在“开发”环节不 仅需要响应“需求”的变化(新增、变更),而且需要响应“设计”的变化。从这个角度来看,“测试”本就应该响应“开发”的变化。

        对于在实际项目自动化测试过程中遇到的上述问题,归根到底是因为“自动化测试方案本身的敏捷程度不够”,主要体现在如下三个方面:

        1、 学习成本高

        测试人员除了要掌握 WebDriver 接口之外,还要掌握 XPath、TestNG 的用法,甚至还需要对功能的前台实现有一定了解。

        2、 脚本维护困难

        敏锐的开发/测试人员从上面的示例脚本中,可以马上嗅出一些“坏味道(Bad Smell)”: 代码相似度非常高、可能变化的数据被硬编码在测试代码里、代码可读性差、测试代码与页面源码耦合度大,等等。这些坏味道的出现,通常意味着需要进行重构, 否则会愈演愈烈,最终变得尾大不掉。

        【注】业界常见的测试工具本质上还是针对页面源码来编写(或生成)测试脚本的,即使提供了录制工具,此类脚本的可读性和后期可维护性还是非常差的。

        3、 断言条件繁琐

        业界常见的测试工具即使提供录制脚本的功能,但是对于“断言”还是需要人工插入的(工具做不到智能的判断我们想要在哪里设置断言),于是断言就成了自动化测试人员的“噩梦”。

        断言对象可能很“多”,页面的信息量往往很大,需要在测试脚本中为每个断言对象(比如,页面某个文本框的值)补充断言语句。

        预期结果是可能“变”化的,甚至是动态的,因此预期结果的值如果与脚本逻辑耦合在一起,将来极难维护。 断言机制比较“呆”板,对于未设为断言对象的字段,如果发生错误也是无法感知的,并且难以对于 UI 样式或 UI 逻辑(比如,翻页图标应该灰显)进行断言。

        换个角度可以理解为,如果这样的断言条件“多”的话,整个测试用例集会“变”的非常“呆”板!

        想要有效的改善这些问题,就必须让自动化测试变得“敏捷”起来!

        在本系列后续的文章中会就“如何让测试脚本易写、易读、易维护”、“如何让断言不再成为测试的负担”、“如何通过持续集成让测试用例发挥更大的价值”进行详细的介绍,敬请关注!

        作者简介

         殷坤,东软集团资深测试经理、技术讲师,10 年软件研发、实施、测试及项目管理工作经验。 目前专注于敏捷项目管理及质量控制、过程改善、自动化测试、持续集成、用户体验提升等方面
来自: InfoQ