软件测试的误区(三)

之前的两篇文章(

和二)介绍了软件测试的基本概念和六个误区。

本文继续给大家带来7个误区的介绍。

误区7: 存在不稳定或者耗时的测试

这种特殊的误区已经被大量记录过,我只是为了完备性而将它包含进来。

由于软件测试是针对回归测试的早期预警,它们应始终以可靠的方式运行。失败的测试应该要引人关注,并且触发相应构建的人员应该立即排查测试失败的原因。

这种方法只能用于具备确定性失败的测试。

一个时好时坏(没有任何代码变化)的测试是不可靠的,并且会破坏整个测试集。

负面影响有两个:

开发人员不再相信测试结果并选择忽略它

即便稳定的测试用例失败了,在一群不稳定测试用例中你也很难发现它

团队中的每个人都应该很容易识别出失败的测试,因为它会改变整个构建的状态。

另一方面,如果存在不稳定测试,很难知道新的失败是否真的是失败,还是不稳定测试导致的。

即使很少的不稳定测试也足以毁掉整个测试集的可信性。

假如你有5个不稳定测试,运行一次构建,出现3个失败。

这并不能直接证明是否一切都ok(因为失败来自不稳定用例集),还是出现了三个bug。

另一个相似的问题是测试运行速度非常慢。

开发人员需要在每次提交之后快速获取反馈(下一节也会详细探讨), 所以缓慢的测试会被逐渐忽略,甚至不再运行。

实际中不稳定和缓慢测试用例基本都来自集成测试和/或UI测试

越靠近金字塔顶层,出现不稳定测试的概率就越大。

众所周知,处理浏览器事件的测试很难保证每次都对。集成测试中的不稳定性原因可能有很多种,但大多数原因都是测试环境及其依赖。

总之,你需要有一个十分可靠的测试集(即使它是整个测试集的一个子集)。

在这个测试集中失败的用例意味着代码真有错,任何失败都意味着代码不能被部署至生产环境。

误区8: 手动运行测试用例

根据你所在组织的情况,可能已经有了几种类型的测试用例。

单元测试负载测试用户验收测试是在代码投入生产之前可能执行的常见测试集类型。

理想情况下,所有测试都应该自动化运行,无需任何人为干预。

如果这是不可能的,至少所有关于代码正确性的测试(即单元和集成测试)都必须以自动化的方式运行。

这样开发人员能够以最及时的方式获得关于代码的反馈。

当你的头脑中代码还没忘记并且你尚未将工作切换到不相关功能时,修复bug会非常容易。

在过去,软件生命周期中最长的步骤是应用部署。

随着基础环境向云演进,机器可以按需创建(虚拟机或容器的形式),获取一台新机器的时间下降至分/秒级。

这种模式转变让很多公司感到意外,因为他们还没有准备好处理日级甚至是小时级的部署。大多数现有的做法都是围绕着冗长的发布周期。

如果公司希望尽快部署,那么在发布中等待手动批准"通过QA"是那些不再适用的过时做法之一。

尽可能快地部署表明你信任每一次部署。

信任自动化部署需要对待部署代码有很高的信心。

虽然有几种方法可以获得这种信心,但第一道防线应该是你的软件测试

然而,配备测试集以快速捕获缺陷只是方程式的一半。另一半是自动化运行测试(最好在每次提交之后)。

很多公司认为他们熟练使用持续发布和/或持续部署。

实际上并不是!

真正成熟的CI/CD意味着在任意时间点都有一个版本的代码可以部署。

因此应用打好包等待"通过QA"并不是真正的CI/CD。

不幸的

尽管大多数公司已经正确意识到应该自动化部署,因为人不仅容易出错还慢,但我仍然看到不少公司采用半手工的流程。

我说的半手工是指尽管测试集已经自动运行了,但是还需要来做些杂活比如准备测试环境或者在测试结束之后清理测试数据。

这是一个误区,因为这并不是真正的自动化。

测试的所有相关方面都应该自动化起来。

有虚拟机或者容器意味着可以非常容易地按需创建多个测试环境。

及时为每个单独请求创建测试环境应该成为你所在组织的标准实践。

这意味着每个新功能可以独立测试。

一个问题很多的功能(也就是测试不通过)不应该阻塞需要同时部署的其他功能的发布。

一个简单的方法可以看出一个公司测试自动化程度,那就是观察QA/测试的工作日常。

理想情况下,测试人员的工作就是向已有用例集添加测试用例。

测试人员本身并不手动执行测试。

测试集由构建服务器运行。

总的来说,测试应该是在幕后的构建服务器上无时不刻的运行着。

开发人员应该在单个功能代码提交之后的5-15分钟获取到测试的结果。

测试人员的工作应该是添加新用例和重构老用例,而不是手动执行用例。

误区9: 把测试代码当做二等公民

如果你是一位经验丰富的开发人员,那么在实现新代码之前,你总会花一些时间来构思它。

关于代码设计有若干哲学,其中一些非常重要,已经有了自己的Wiki索引。

比如:

DRY

KISS

SOLID

第一条是颇具争议的、最重要的一个,因为它强迫你无重复地实现代码然后在多个功能中进行复用。

根据编程语言的不同,你可能还看到过其他的最佳实践原则和推荐设计模式。

你们团队可能还有自己的指导原则。

然而,由于某些未知的原因,一些开发人员并不会在软件测试的代码上采用这些相同的原则。

我见过有的项目有着设计良好的功能代码;

但是测试代码中却遍布代码重复、硬编码变量、复制-粘贴片段和其他任何不允许在主代码中出现的低效问题。

把测试代码当做二等公民是说不通的,因为长远来看所有的代码都需要维护。

测试在未来需要更新和重构。

其变量和结构会变化。

如果写测试时不考虑他们的设计,那么你的技术债务就会增加,并累加到主代码已有的债务之中。

尽量像你在设计功能代码一样设计你的测试代码。

所有通用的重构技术也都应该用在测试代码中。

作为一个起点:

所有的测试创建代码应该集中化。

所有的测试应该采用相同的行为创建测试数据

复杂的验证代码片段应该抽出作为一个常用域内的特殊库

多次使用的mock和stub也不应该拷贝-粘贴

测试初始化代码应该在相似测试之间共享

如果你采用了静态分析、代码格式化或这代码质量工具,那么同样也在测试代码上配置使用。

总之,就是设计你的主功能代码一样设计你的测试代码细节。

误区10: 没有把产品bug转化测试用例

测试的目标之一是发现缺陷。

正如我们在误区4看到的,大多数应用都有一部分"关键"代码,大多数的bug都从其中产生。

当你修复一个bug之后你要确定它不会再出现。

达到这个目的的最好方式之一就是为该修复编写测试用例(无论单元测试还是集成测试或者都用)

产品中出现的bug是编写软件测试用例的绝佳参考:

它们表明该部分缺少测试以致于bug已经出现在生成上

如果你给这写bug写用例,那么其价值就很明显,因为他们防止软件的未来发布中再次出现。

我一直很惊讶有的团队(尽管有完备的测试策略)不为 产品 中发现的bug编写测试用例。

他们直接改正代码并修复bug。

由于某些奇怪的原因,很多开发者认为只有在增加新功能的时候编写用例才有价值。

简直大错特错。

从实际bug中获取的测试用例甚至要比新部署中新加入的用例有价值的多。

毕竟你绝不会知道生产环境中的一个新功能的出错频率是多少(也许它是一个从不出错的非核心代码)。

有测试用例是好的,但是价值就值得质疑。

另一方面,为实际bug编写的测试用例价值就大得多。

不仅因为它验证了你的修复正确,还保证当相同区域发生重构时,你的修复依旧有效。

如果你加入了一个没有测试用例的老项目,这也是最明智地发挥软件测试价值方式。

比起猜哪部分代码需要测试,你更应该关注已有的bug并用用例来覆盖它们。

不久之后你的测试用例就可以覆盖代码的关键部分,因为根据定义来说,所有的测试都验证了经常出错的东西。

我建议的评价指标之一就包含了这个情况。

可以接受写测试用例的唯一情况是你在产品中发现的bug与代码无关,而是由环境导致的。

比如负载均衡的配置错误就不是靠单元测试能解决的。

总之,如果你不确定应该给哪些代码写测试用例,那就看看产品中出现的bug。

误区11: 拿TDD当做信仰

TDD,测试驱动开发(Test Driven Development)

像其他之前的理论一样,在布道师尝试说服公司无脑采用TDD作为唯一的开发模式之前,是一个很好的理论方法。

撰写本文的时候这个趋势正在消失,但是我决定为了完备性提一下(因为这个企业界受这个误区的影响尤其严重)。

宽泛地说,当提到软件测试时:

你可以在实现相应代码之前编写测试用例

你可以在实现相应代码同时编写测试用例

你可以在实现相应代码之后编写测试用例

你可以在实现相应代码时不编写测试用例

TDD的核心原则之一是从是遵守第1项(在实现相应代码之前编写测试用例)。

在编写代码之前编写测试用例是一个好的实践但并不总是最佳实践。

在实现代码前编写测试暗含了你很确定你的最终API,而这不一定。

也许你面前有一份条理清晰的文档,因此你知道所要实现的接口的代码的每一个细节。

但是其他情况下,你大概只能通过尝试一些事情,快速实验并向最终方案靠拢,而不是上来就有解决方案。

举一个更实际的例子,说明在初创公司盲目采用TDD是不成熟的。

如果你工作在一个初创公司,你写的代码可能需要快速迭代,那么TDD是帮不上什么忙的。

能够"通过"的代码你甚至可能想扔掉。

本例中最好的策略是在实现代码之后编写测试。

不写测试(第4项)也是一个有效的策略。

正如我们在误区4中看到的,有的代码不需要测试。

给零碎代码写测试用例因为这是"TDD"的正确方式,但会让你无功而返。

无论如何,TDD狂热者无论什么情况下都痴迷于测试优先,是对理性开发者心理健康的巨大损害。

这种痴迷已经在很多地方有记录,所以我希望在这个话题无需多言(搜索"TDD is crap/stupid/dead")。

这里我得承认有时候我会像下面这样实现代码:

先写主功能

然后写测试

运行测试成功

注释功能的核心代码

运行测试失败

取消注释代码,恢复至原始状态

运行代码又一次成功

提交代码

总的来说,TDD是一个好的理念,但是你不必每次都都遵守。

如果你工作在世界500强,周围都是严谨的业务分析和清晰的实现要求,那么TDD大概是有用的。

反过来,如果你只是周末在家玩一玩新框架,了解它的工作原理,那还是不要遵守TDD了。

误区12: 不看说明书直接写测试用例

一个专业的开发人员需要对工作所需技能有充分了解。

在一个项目开始之前你需要花费额外的时间去学习你要使用的技术。

web框架层出不穷,先了解它的功能能写出更清晰高效的代码。

你应该同样尊重软件测试。

因为有些开发人员把测试看低一等(误区9),他们从不肯坐下来好好学习一个测试框架的功能。

而是从其他项目复制测试代码和例子,看一眼差不多能跑就行。

但这不是一个专业人员应有的行为。

不幸地是,这种情况太多了。

有人写了几个"辅助函数"和"公用实体",但是却没有意识到测试框架已经通过内置或者插件化的方式提供了相同功能。

这些公用实体使得测试难以理解(特别是对于初级开发人员), 因为其中充满了一些团队内才知道的、无法在团队/公司间传递的知识。

我曾经几次用现成的标准库替换掉"聪明的内部测试方案",两者能做一样的东西,但是后者更标准

你应该花时间学习测试框架的功能。

比如尝试发现他能做:

测试参数化

mock和stub

测试setup和teardown

测试分类

测试条件运行

如果你也开发典型的web应用,为了理解相关最佳实践你最少需要了解:

测试数据生成器

HTTP客户端库

HTTP模拟服务器

变异/模糊测试

DB清理/回滚

负载测试等

没有必要重新造轮子。

这句话对测试也适用。

也许你的应用确实很独特,存在一些极端情况,确实需要团队内部造一些公用实例。

但是我敢打赌你的单元集成测试根本就没啥特别的,因此写一些独特的测试用例是很令人怀疑的实践。

误区13: 因为无知而认为测试不好

虽然这是本文的最后一个误区,但这才是我写这篇文章的原因。

当我在会议和聚会上碰到一些宣称所有的测试都是浪费时间并且他们的应用压根不写应用也能很好运行的人的时候,我很失望。

更多情况是一些人反对某种特定类型的测试(通常是单元或者集成测试),就像误区1和误区2中看到的那样。

当我看到这类人时,我喜欢用几个问题来了解他们,去获取他们讨厌测试的原因。

毫无意外地,都能归结到误区中。

比如:

他们之前工作的公司测试速度很慢(误区7),

需要不断重构(误区5)。

被毫无理由地要求100%的测试覆盖率(误区6)

被想要把自己扭曲理解的TDD强加到整个团队中的TDD狂热者(误区11)"伤到"。

如果你是上面这些人中的一个,那我真的理解你。

我明白在这些有坏习惯的公司里工作是多么艰难。

过去项目里测试工作的不好体验不应该干扰你在测试新项目时的判断。

公正地看待你的团队和项目,看看其中是否存在什么误区。

如果有,那么是在以错误的方式进行测试,没有任何测试会让你的应用程序更好。

很遗憾但这是真的。

团队遭受不良测试习惯影响是一回事,教导初级开发人员"测试就是浪费时间"是另一回事。

不要做后者!

有的公司并没有陷入本文提到的任何一个误区。努力找到他们吧!

Qtest是360旗下的专业测试团队!

是WEB平台部测试技术平台化、效率化的先锋力量!

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180917B1L7GC00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券