微服务如何测试?

前言

测试是我们的代码发布至生产环境前最重要的一关,那么如何高效、有效测试缺留下了一些疑问,接下来我们简单聊一聊。

测试分类

面向业务
1:验收测试
我们是否实现了正确的功能?

2:探索性测试(手工)
可用性测试,我如何破坏系统功能

支持团队
1:验收测试
我们是否实现了正确的功能?

2:单元测试(xunit系列框架)
我们是否正确地实现了功能

评价产品
1:探索性测试(手工)
可用性测试,我如何破坏系统功能

2:非功能性测试(工具)
响应时间、可扩展性、性能测试、安全测试

面向技术
1:单元测试(xunit系列框架)
我们是否正确地实现了功能

2:非功能性测试(工具)
响应时间、可扩展性、性能测试、安全测试

面向技术测试:即那些首先能够帮助开发人员构建系统的测试。这里面大部分都是可以自动化的,例如性能测试和小范围的单元测试。

面向业务测试:主要是帮助非技术背景的相关人群,了解系统是如何工作的。这种测试包括大范围、端对端的验收测试,还有由用户代表在UAT系统上进行手工验证的探索性测试。

对于上面的每种测试类型都有自己相应的位置。在不同系统中,每种类型的测试占比是有差别的。重点是要理解你在测试方面可以有不同的选择。放弃大规模的手动测试,尽可能地使用自动化是近年来业界的一种趋势。

测试范围

《Scrum敏捷软件开发》一书中介绍了一种叫作测试金字塔的模型,其中描述了不同的自动化测试类型。这个金字塔模型不仅可以帮助我们思考不同的测试类型应该覆盖的范围,还能帮助我们思考应该为这些不同的测试类型投入多大的比例。如下图所示,模型中把自动化测试划分为单元测试、服务测试和用户界面测试三层。

微服务如何测试?_第1张图片
困惑解答
1:只测试一行代码是单元测试么?是
2:测试多个函数或者多个类仍然是单元测试么?不是,不过很多人并不同意

单元测试
单元测试通常只测试一个函数和方法调用。通过TDD(测试驱动开发)写的测试就属于这一类,由基于属性的测试技术所产生的测试也属于这一类。在单元测试中,我们不会启动服务,并且对外部文件和网络连接的使用也很有限。通常情况下你需要大量的单元测试。如果做得合理,它们运行起来会非常快,在线上硬件环境上运行上千个测试,可能连一分钟都不需要。

单元测试是帮助我们开发人员的,是面向技术而非面向业务的,我们也希望通过它们来捕获大部分的缺陷。单元测试是彼此独立的,分别覆盖一些小范围的代码。

主要目的:能够对于功能是否正常快速给出反馈。单元测试对于代码重构非常重要,因为我们知道,如果不小心犯了错误,这些小范围的测试能很快地给出提醒,这样我们就可以放心地随时调整代码。

服务测试
服务测试是绕开用户界面、直接针对服务的测试。在独立应用程序中,服务测试可能只测试为用户界面提供服务的一些类。对于包含多个服务的系统,一个服务测试只测试其中一个单独服务的功能。

只测试一个单独的服务可以提高测试的隔离性,这样我们就可以更快地定位问题并解决问题。为了达到这种隔离性,我们需要给所有的外部合作者打桩,以便把服务本身保留在测试范围内。

一些服务测试可能会像单元测试一样快,但如果你在测试中使用了真实的数据库,或通过网络跟打桩的外部合作者交互,那么测试时间会增加。服务测试比简单的单元测试覆盖的范围更大,因此,当运行失败时,也比单元测试更难定位问题。不过,相比更大范围的测试,服务测试中包含的组件少多了,因此也没大范围的测试那么脆弱。

端对端测试
端对端测试会覆盖整个系统。这类测试通常需要打开一个浏览器来操作图形用户界面(GUI),也很容易模仿类似上传文件这样的用户交互

这种类型的测试会覆盖大范围的产品代码。因此,当它们通过的时候你会感觉很好,会确定这些被测试过的代码在生产环境也能工作。但是随着覆盖范围的增大,一些在使用微服务过程中很难消除的负作用也会随之而来。

权衡
在使用金字塔时,应该了解到越靠近金字塔的顶端,测试覆盖的范围越大,同时我们对被测试后的功能也越有信心。缺点:因为需要更长的时间运行测试,所以反馈周期会变长。并且测试失败时,比较难定位哪个功能被破坏。

而越靠近金字塔的底部,一般来说测试都会很快,所以反馈周期也会变短,测试失败后更容易定位被破坏的功能,持续集成的构建时间也很短。从另一个角度来看,当只测试了一行代码时,我们又很难有充足的信心认为,系统作为一个整体依然能正常工作。

当范围更大的测试(比如服务测试或者端对端测试)失败以后,我们会尝试写一个单元测试来重现问题,以便将来能够以更快的速度捕获同样的错误。我们通过这种方式来持续地缩短反馈周期。

比例
既然所有的测试都有优缺点,那每种类型需要多大的占比呢? 一个好的经验法则是,顺着金字塔向下,下面一层的测试数量要比上面一层多一个数量级。如果当前的权衡确实给你带来了问题,那可以调整不同类型自动化测试的比例,这是非常重要的。

实现服务测试

在自动化测试的所有类型中,单元测试是比较简单的,相关的资料也非常多。而服务测试和端到端测试的实现则要复杂得多。

服务测试只想要一个测试一个单独服务的功能,为了隔离其他的相关服务,需要一种方法给所有的外部合作者打桩。

对于每一位下游合作者,我们都需要一个打桩服务,然后在运行服务测试的时候启动它们。我们还需要配置被测服务,在测试过程中连接这些打桩服务。接着,为了模仿真实的服务,我们需要配置打桩服务为被测服务的请求发出响应。

mock还是打桩
打桩:是指为被测服务的请求创建一些有着预设响应的打桩服务。比如我可能会设置积分账户,当有请求询问客户123的余额时,它应该返回15000。这时候测试不关心打桩服务被访问了0次、1次、还是100次。

mock:与打桩相比,mock还会进一步验证请求本身是否被正确调用。如果与期望请求不匹配,测试变回失败。这种方式的实现,需要我们创建更智能的模拟合作者,但过度使用mock会让测试变得脆弱。相比之下,打桩并不在乎请求发生了0次、1次还是很多次。

无论打桩还是mock,我们可以把它们理解为测试替身。

智能的打桩服务
相信大家在测试过程中每次打桩都需要把之前做过的事重复做一遍,那么如果有一个打桩服务可以替代我们做这些重复性的工作,那真是太棒了。

还有一种方式是你在开发过程中,提前预留好打桩的口子,当你需要时,只需要修改下配置启用它,这也是一种不错的方案,相对于每次用时才做这件事灵活许多。

微秒的端到端测试
直接在客户服务流水线的最后增加这些测试

让多个流水线扇入(fan in)到一个独立的端到端测试的阶段(stage)。使用这种方法,任意一个服务的构建都会触发一次端到端的测试。一些更好地支持构建流水线的CI工具可以很方便地实现这样的扇入模型

端对端测试的缺点

1:脆弱性
2:运行缓慢
3:反馈周期长

脆弱的测试

随着测试范围的扩大,纳入测试的服务数量也会相应的增加。这些服务有可能会使测试失败,而这种失败并不是因为功能真的被破坏了,而是由其他一些原因引起的。比如说网络故障、或者说验证一个功能,涉及多个服务,某个服务停止运行都会导致失败。

包含在测试中的服务数量越多,测试就会越脆弱,不确定性也就越强。如果测试失败后每个都只是想重新运行一遍测试,然后希望有可能通过,那么这种测试是脆弱的。不仅这种涉及多个服务的测试很脆弱,涉及多线程功能的测试通常也会有问题,测试失败有时是因为资源竞争、超时等,有时是功能真的被破坏了。

当发现脆弱的测试时候,应该立即记录下来,当不能立即修复时,需要把它们从测试套件中移出,然后就可以不受打扰的修复它们。修复时,首先看看能不能重写来避免被测代码运行在多个线程中,再看看是否能让运行的环境更稳定。更好的办法是,看看能否用不易出现问题的小范围测试取代脆弱的端对端测试。有时候,改变被测软件本身以使之更容易测试也是一个正确的方向。

谁来写这些测试
如果这些测试是某服务流水线的一部分,一个比较合理的想法是,拥有这些服务的团队应该写这些测试。

测试多长时间
运行端对端测试需要很长时间。一个测试套件需要花整整一天的时间运行,然后经常有与功能破坏无关的测试失败,这就是个灾难。

可以通过并行运行测试来改善缓慢的问题、或者去掉一些不必要的测试(这种方式可能会带来一些风险,当你决定要用方式时,要考虑清楚)

大量的堆积
端到端测试的反馈周期过长,不仅会影响开发人员的生产效率,同时任何失败的修复周期都会变长,这也就不可避免地减少了端到端测试通过的次数。如果只有在所有测试通过的前提下才能部署软件,那么服务被部署的次数也会减少。

这可能会导致大量的堆积。在修复失败的端对端测试的同时,上游团队一直提交更多的变更。

部署的变更内容越多,发布的风险就越高,我们就越有可能破坏一些功能。保障频繁发布软件的关键是基于这样一个想法,尽可能频繁地发布小范围的改变。

元版本
把多个服务一起进行部署,并给系统一个版本号。这样会导致服务的耦合,不用很长时间,本来分离得很好的服务就会与其他服务纠缠得越来越紧密。

测试场景,而不是故事

尽管如上所述的缺点,但对许多用户来说,覆盖一两个服务的端对端测试还是可管理的,也是有意义的。但是,随着服务的慢慢增多,为每一个新添的功能增加一个新的端对端的测试,不久,我们的测试套件将会变的非常臃肿、很长的反馈周期、巨大的重叠测试覆盖率。

解决这个问题的最佳办法是,把测试整个系统的重心放到少量核心的场景上来。把任何在这些核心场景之外的功能放在相互隔离的服务测试中覆盖。

消费者驱动测试

使用之前所提到的端对端测试,我们试图解决的问题是什么?是试图确保部署新的服务到生产环境后,变更不会破坏新服务的消费者。有一种不需要使用真正的消费者也能达到同样目的的方式,它就是CDC(消费者驱动的契约)

当使用CDC时,我们会定义服务(或生产者)的消费者的期望。这些期望最终会变成对生产者运行的测试代码。从测试反馈的周期来看,只需要针对生产者运行这些CDC测试,所以它比要解决同样问题的端对端测试更快,也更可靠。

还应该使用端对端测试么?

虽然说了很多端对端测试的缺点,但是并不意味着端对端测试应该全部扔掉。

可以把端对端测试当做服务部署到生产环境的辅助轮。

部署后再测试

大多数测试会在系统部署到生产环境之前完成。我们通过测试定义一系列的模型,希望证明在功能需求和非功能需求方面,系统的工作方式和行为都符合预期。但如果我们的模型并不完美,那么系统在面对愤怒的使用者时就会出现问题。缺陷就会溜进生产环境,新的失效模式就会出现,用户也会以我们意想不到的方式来使用系统。

我们对此通常的反应是,定义更多的测试来完善我们的模型,以便将来尽早捕获更多的问题,从而减少发生在生产环境中缺陷的次数。我们必须承认,使用这种方法得到的收益会逐渐减少。仅仅靠部署之前进行的测试,我们不可能把缺陷率降为零。

区分部署和上线
在更多问题发生之前捕获它们。要达到这个目的,一种方式是突破传统的在部署之前运行测试的方法。如果可以部署到生产环境,在真正有负载之前运行测试,我们可以发现特定环境中的问题

一个常见的例子是,用来验证部署后的系统是否是正常工作的、针对新部署软件的一系列冒烟测试套件。这些测试帮助我们识别与环境有关的任何问题。

另一个例子是所谓的蓝/绿部署时,我们会部署两份软件,但只有一个接受真正的请求。比如说老系统是123,新系统是456,两个同时部署,当前只有123接受请求,我们先对456进行测试,如果测试没有问题,再把123请求的流量切到456上。

如果用蓝/绿部署这种方案有几个前提条件
1:需要能够切换生产流量到布偶听的主机(或主机集群)上。切换可以通过改变DNS条目,或更改负载均衡的配置。
2:需要提供足够多的主机,以支持运行两个版本的微服务。

保持旧版本的运行,除了给予我们切换生产流量前可以测试服务这个好处外,还可以大幅度的减少发布软件所需要的停机时间。使用某些生产流量重定向的机制,我们甚至可以做到在客户无感知的情况下进行版本切换,达到零宕机部署。

金丝雀发布
金丝雀发布是指通过将部分流量引流到新部署的系统,来验证系统是否按预期执行。按预期执行可以涵盖很多内容,包括功能性和非功能性的。例如,我能可以验证新部署服务的请求响应时间是否在500ms以内,或者查看新服务和旧服务是否有相同的错误率比例。甚至更进一步,如果我们要发布一个新版本的推荐服务,可以同时运行两个版本,然后看看新版本的推荐服务是否能够达到预期的销售量,以确保我们没有发布一个次由算法的服务。如果新版本没有达到预期,我们可以迅速恢复到旧版本。如果达到了预期,我们可以引导更多的流量到新版本。

与蓝/绿发布的不同之处在于,新旧版本共存的时间更长,而且经常会调整流量

平均修复时间胜过平均故障时间
通过使用蓝/绿部署和金丝雀发布及时,我们找到了方法,在类生产环境上测试,我们还构建工具来帮助管理有可能发生的失败。

有时花费相同的努力让发布变更变得更好,比添加更多的自动化功能测试更加有益。在web操作的世界,这通常被称为平均故障间隔时间和平均修复时间之间的权衡优化。

减少修复时间的技术可以简单到尽快回滚加上良好的监控,类似蓝/绿部署。如果我们早点发现生产中的问题,尽快回滚,就可以减少对客户的影响。我们还可以使用蓝/绿部署技术,部署一个软件的新版本,在生产环境对它进行测试后再引导用户到新版本。

跨功能的测试

非功能性需求,是对系统展现的一些特性一个总括的术语,这些特性不能像普通的特性那样简单实现。它包括以下方面,比如一个网页可以接受的延迟时间、系统能够支持的用户数量,用户界面如何让残疾人也可以访问,或者如何保障客户数据的安全。

性能测试
性能测试作为一个跨功能需求的一个方法是值得明确说明的。将系统拆分为较小的微服务后,跨网络边界调用的次数明显增加了。之前操作可能只涉及一次的数据库调用,现在可能涉及三四次跨网络边界来调用其他服务,还有匹配数量的数据库调用,所有这些调用都可能减缓系统操作的速度。因此,追踪延迟的根源显得尤其重要。当有多个同步的调用链时,链的任何部分变得缓慢,整个链都会受影响,最终会对整体速度有明细的影响。这使得用一些方法对微服务系统进行性能测试,比单块系统更重要。

与功能测试类似,性能测试也可以是各自范围测试的混合。你可能决定想测试单个独立服务的性能,但开始的时候,可以用测试来检查系统中的核心场景的性能。你可以简单地使用端到端的场景测试,然后大量并发运行。

注意
1:性能测试的环境配置要和生产环境的配置一致
2:测试运行完后一定要结果
3:性能测试需要与系统性能的监控同时进行

总结

本文主要是针对如何测试系统,起到一般性的指导作用。

&《微服务设计》

你可能感兴趣的:(微服务,测试,微服务测试)