一次Testing in Production方案的探索

引子

传统的软件测试大多是在测试环境下进行的。人们普遍认为生产环境是服务于最终用户的,只有在测试环境下进行充分测试后才会发布给用户。

基于非生产环境的测试-单元测试、集成测试、功能测试等,很多都是基于预期结果的测试,测试人员一般是带着这样的思路来工作 “如果这样做会发生什么呢” -属于known-unknowns。而生产环境往往充满了惊喜-属于unknown-unknowns。我们不知道最终用户怎么操作(参考同事姚琪琳的文章《被踢出去的用户》),数据是什么样的,基础设施有什么差异等。

Stage作为类生产环境,是和生产环境最接近的一个测试环境。然而每一次新的发布都是一组代码和环境的组合,只有真正部署到了对应的环境,我们才能确定到底有没有问题。Stage环境也是测试环境,是抱着一定目的进行的操作,并不能完全反应真实用户的行为。

项目背景

我们当前的测试流程如下:

产品经受了多个测试环境的考验,但是在部署生产环境后依然暴露出很多意想不到的问题,初步分析后归结为下面两个因素:

1. 生产环境下数据复杂多样

我们对客户报过来的production问题进行了分析,下图可以看出由于数据问题导致的功能/性能问题在10%左右的区间波动。

软件系统的灵活性给与了用户各种各样的操作可能,代码/脚本的不确定性也会造成数据的不一致。这些都赋予了生产环境下数据的多样性,是其他环境无法模拟的。

2. 软件配置的集中化

ThoughtWorks团队主要负责软件的开发,而Stage和Prod环境部署在云平台上,这些访问权限严格控制在客户手中,基础设施严重依赖于客户。

项目即将大规模将配置由原来的SVN迁移到ZooKeeper实现集中管理。作为一个技术的改进,同时也蕴含着风险 – Stage和Prod的配置将由客户进行单点手工维护,对ThoughtWorks团队不可见,因此我们无法预知某个配置是否已经被添加/修改以及是否赋予了正确的值。

Testing in Production如何做

环境的特殊性带来了产品的不确定性,我们希望把测试的触角向前延伸,到生产环境去做测试,提前暴露产品的潜在问题,提高用户的满意度。

由于各种因素的约束,在生产环境能做的事情往往有限。比如我们项目的安全等级很高,开发团队是不能够访问生产环境的服务器的,甚至连脱敏的数据也接触不到。Stage环境下的数据也仅仅是客户的测试数据,不能把生产环境下的数据迁移过来。

业界实践

TiP并不是一个全新的事物,业界已经有了很多成熟实践:蓝绿部署、金丝雀测试、A/B测试等。

蓝绿部署是在有两个一样环境的前提下,不停老版本,部署新版本进行测试。测试没问题之后直接把流量切到新版本上,再把老版本也升级到新版本。一般适用于对用户体验有一定的忍耐度、机器资源丰富的团队。

“金丝雀测试”得名于以前旷工下井前会先放一只金丝雀去看是否有有毒气体,以金丝雀能否存活进行判断。一般是部署新版本到很小比例的服务器上,并允许小部分用户来使用新版本,测试通过则把剩余的服务都升级为新版本。一般适用于对新版本缺乏信心的团队。

A/B测试主要用于产品功能对比,版本A和版本B分别部署在不同的服务器上并开放给不同的用户使用,一般适用于收集用户反馈辅助产品功能设计。

蓝绿部署

基于当前产品环境的复杂架构,构建另一套相同的生产环境来实现蓝绿部署作为第一方案被提出来。蓝绿部署的思路如图:

在同一个时间段,蓝作为当前的生产环境供线上用户使用,绿作为部署新功能的测试环境供部分用户使用。两个环境的基础设施相同,配置一样,数据都是真实的生产环境数据。绿环境下发现的问题可以随时诊断修复,确认满足上线需求后即可把线上用户引流到绿环境,实现了最小化的宕机时间。

蓝绿部署的这个优势看似极好的契合了项目当前的诉求,但是准备一套同样的生产环境需要的成本在可视化出来之后也是令人震惊的!新的服务器就需要7台,而且每个月还需要预留出足够的时间来同步数据。在功能交付的压力之下,客户是不会为这样一个昂贵且成果未知的方案买单的,我们连自己都说服不了。

改进的方案

就在焦灼的时候,在一次头脑风暴中我们获取到一条线索-客户的灾备环境(Disaster Recovery)在定期从生产环境同步数据,但也仅仅是同步数据,代码已经很久没有部署过。也就是说灾备环境没有真正起到它应有的灾难备份和恢复,只是一个数据的备份而已。

方案就此而得到转机 – 是否可以复活灾备环境,利用它可以访问生产环境数据的天然优势来解决前面的痛点呢?在蓝绿部署方案的基础上,改进的方案如下:

鉴于灾备环境的基础设施不足以支撑其作为线上环境供所有用户使用,但是它的配置是等同于产品环境的。DR的定位为分时的灾备和测试环境 – 大部分时间用于灾备,小部分时间作为金丝雀进行新版本的测试。

灾备环境测试通过后的版本按照当前的部署流程进行生产环境的部署。这样一来不仅能恢复其本来的灾备作用,也解决了之前数据和配置集中化问题带来的痛点。

展望

从当前的测试流程来看,QA和Stage环境承担的工作有很大一部分重叠,带来了一定的浪费。希望未来有一天能去掉Stage环境,直接把这些server用在生产环境下构建一套新的环境,做到充分的基于生产环境的测试,实现新老版本的无缝切换。 期待测试流程会变成如下所示:

当今软件的部署越来越多的基于第三方的云平台,给团队带来了不可控因素。Testing in Production是基于生产环境下真实用户的行为和数据进行的一系列QA活动。传统的基于测试环境进行的测试活动,辅助以生产环境下的QA活动为提高软件的质量注入了新的活力。


更多精彩洞见,请关注微信公众号:思特沃克

Share

一个遗留系统自动化测试的七年之痒

背景

项目从2009年开始启动,采用的是TDD开发方式。在这之后的过程中,团队做过各种尝试去调整自动化测试的策略去更好的适应不同阶段项目的特征,比如调整不同类型测试的比例,引入新的测试类型等。

七年之痒 – 痛点

随着项目走到了第七个年头,一系列的变化在不断发生,比如技术上引入了微服务、EventStore等,业务变得越来越复杂,子系统变得更多,更多的人员加入,开始实施按月发布等,这些因素交织在一起凸显出自动化测试的滞后。首先是从团队成员感知到的一些痛点开始的:

质量下降 – 这个体现在部署到测试环境的代码质量较差,常常就是新版本部署上去之后某个核心功能被破坏,要么是新功能破坏了老功能,要么是bug的修复把其他功能破坏。

测试不稳定 – QA有很长的时间在等待修复或新功能提交出包,而这个等待可能是几个小时也有可能是几天。除去网络问题、部署流水线的复杂性等因素,自动化测试的不稳定性也导致出包的速度也受到了影响。大家往往更关注于怎么能把测试通过了能够出一个包,却忽略了我们该怎么去处理一个不稳定的测试。下图的run2, run3正是大家在不断的尝试去rerun挂掉的测试。

1-rerun

团队越来越忙,开始陷入恶性循环 – 随着功能的逐渐增多,每个月上线的回归测试列表越来越长,QA需要花更多的时间去做重复的回归测试,新功能的测试和回归测试的压力都很大,甚至有的时候根本都没有时间去review下一个阶段的需求,更别提其他一些更有价值的事情。往往回归测试做不完就不得不往下一个阶段推,这种不断往复导致大家对发布的产品信心严重不足。

在这种情况下,自动化测试的有效性和完备性都受到了质疑。本来期望自动化测试能够帮助我们构建一张安全的防护网,保证主干业务不被破坏;随着pipeline频繁的去执行,及时反馈问题,不要等到测试环境才暴露出来;同时能够把QA重复的手工测试时间释放出来,去做一些更有价值的事。可是根据团队所感受到的痛点,我们觉得自动化测试不仅没有帮助到我们,反而在一定程度上给团队带来了干扰。

问题分析

自动化测试到底出了什么问题?我们从现有UI测试入手开始分析,发现了以下典型现象:

  1. 最有价值的场景没有被覆盖
    虽然有较多的测试场景,但是体现出核心业务价值的场景却稀少。我们都知道80/20原则,用户80%的时间在使用系统中20%的功能,如果大部分的UI测试是在测另外那80%的功能,这样一个覆盖给团队带来的安全指数是很低的。
  2. 失效的场景
    功能已经发生了变化,可是对应的UI测试并没有变,至于它为什么没有挂掉,可能有一些侥幸的因素。比如现在点了确认按钮之后新增了弹窗,而测试并没有关掉弹窗,而是通过URL跳转到了别的页面,也没有验证弹窗的新功能是否工作,既有的实现方式确实会使得测试一直通过,但是没有真的验证到正确的点。
  3. 重复的测试
    同样的测试在UI层跟API层的重合度较高,有的甚至是100%。比如搜索用户的功能,分别去按照姓、名、姓的一部分、名的一部分、姓+名等等各种组合去验证。我们不太清楚在当时是基于怎样的考虑留下了这么多跟UT/API测试重复的用例,但是在现阶段分析之后我们觉得这是一种没必要的浪费。 另外,不同的测试数据准备都是UI测试执行出来的,很多场景都用到了相同的步骤,我们觉得这也是一种重复,可以通过其他方式来实现。

解决问题

这些问题从某种角度上都暴露出了UI测试年久失修,没有得到好的维护,而新功能的自动化测试又在不断重蹈覆辙,问题积攒到一起暴发出来使得大家开始重视自动化测试。 有痛点并且找到问题了,下一步就是解决问题。我们分了两步来走,第一个就是对已有UI测试的优化,第二个是对新功能的自动化测试策略的调整。

已有UI测试的优化

针对已有的自动化测试,再回过头逐个去审查UT/API/UI测试代价太高,我们只能是从UI测试优化入手。针对前面提到的3个问题我们逐一去攻克:

  • 识别系统关键业务场景
    我们首先挖掘出系统中用户要达到的各个业务目标,根据不同的业务目标梳理出不同的业务场景,然后将这些发给客户去评审。客户对我们总结出的这些场景很认可,只是把每个业务目标都赋予了一个优先级,为后续的编码实现给予了参考。
  • 重新设计测试场景
    UI测试不是着重去测试某个功能是否工作,更关注的是用户在使用系统时能否顺利实现某个业务目标,因此我们需要知道用户是怎么使用系统的。同样的目标,可能会有多个途径来完成,通过跟客户的访谈以及观察产品环境下页面的访问频度,我们重新规划了测试场景,期待能更贴近用户的真实行为,及时防御可能会导致用户不能顺利完成业务目标的问题。
  • 优化测试数据准备,删除重复的测试
    对于UI层的过度测试,直接删除和API/UT层重复的测试,保留一条主干路径用来测试系统连通性。 对于不同的业务场景可能需要准备的数据,我们舍弃了之前通过UI执行测试这种成本高的方式,转而以发API请求的方式来准备,这样降低了测试执行的时间,也使得测试更加的稳定。 重新梳理的测试场景帮我们构建了一张较为全面的安全防护网,覆盖了绝大部分的用户使用场景,大家的信心显著提高。如果核心业务受到破坏,立马就可以通过UI测试反馈给相关的人。执行更加稳定的测试也减少了大家对UI测试是否能真的发现问题的质疑,因为随机挂的频率大大的降低,一旦挂可能就是真的有bug了。

新功能自动化测试策略的调整

我们一般会以测试金字塔作为自动化测试的指导策略,下图是我们项目的测试金字塔。

2-test-pyramid

为了避免新功能步旧功能的后尘,我们对自动化测试策略进行了调整。除去以不同层的数量分布来判定策略是否合理外,我们也更看重在这个数量下关键业务场景是否能被有效地覆盖,主要通过下面两种方式来保证:

  1. QA及早介入自动化测试
    质量是需要内建的,不是测出来的。QA从一开始就介入整个流程之中,在story启动的时候会和DEV一起准备任务拆分。在后期验收story的同时也会验收单元测试,确保能在UT/API/Contract层实现的测试都在这些层面覆盖,不仅保证了底层测试的数量要够多,也确保了这么多测试覆盖的点都是合理有效的。在这个过程中,QA把更多的测试思路传递给团队成员,引发大家更多的从质量角度去思考。
  2. QA与DEV结对写UI测试
    最后在整个功能做完的时候,QA也会和DEV结对实现UI测试,涉及到现有测试场景的维护与更新。基于前面对底层测试的review,大家对于整个功能的测试覆盖都有了一定程度的了解,对于UI测试要测得点也会较快的达成一致。另外,QA在与DEV结对实现UI测试的时候,编码能力也得到了提高。

在推动QA更多参与底层测试的过程中,我们更多的从测试角度去影响团队,增加了团队的质量意识。QA的时间被释放出来了,去做了更多有价值的事,比如探索性测试,Log监控与分析,安全测试,产品环境下用户行为分析等。这一些列活动的影响就是产品的质量顺便得到了提升。

总结

等我们把已有功能UI测试优化完,新功能的自动化测试策略开始落实到全组,已经是半年以后的事了。我们慢慢的感受到一切都在回归正轨,之前的痛点在逐步消去,团队交付的节奏也越来越顺畅,对发布产品的信心也更强了。

回顾这个遗留系统的自动化测试优化过程,我们有一些收获:

  1. 大家说到UI测试往往更倾向于如何编码实现,但我们希望开始UI测试的时候能多关注下测试用例的设计是否合理,是不是能够体现出业务价值。
  2. UI测试的用例和代码都是测试资产,需要跟产品代码等同对待,不能写出来就不管不顾,没有维护是不可取的。
  3. 自动化测试不仅仅是UI测试,需要和UT/API等其他底层测试一起分工合作,作为测试策略的一部分来为产品质量保驾护航。
Share