前几周给一个客户介绍了关于持续交付的内容,他们特别关注概念以及细节,当时我在介绍自动化测试的时候,
  分类比较简单,感觉回答的不是很好,于是重新整理下思路,总结成文。
  软件测试的定义
  对于软件测试的定义不尽相同,其经典定义是:在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。
  软件测试的分类
  测试的分类很多,可以按照不同的角度去划分,比如
  1.功能/非功能测试
  2.静态/动态测试(是否执行程序)
  3.白盒/黑盒/灰盒测试(是否关心软件内部结构和具体实现)
  4.按照软件开发过程划分
  我自己更倾向于按照软件开发的过程去划分,同时,将测试的范围扩大到软件的整个生命周期,不止是开发测试,还要包括部署、产品的监控以及对基础架构的测试。如此,则有以下(不限于)分类:
  Unit Testing
  Integration Testing
  Functional Testing
  Smoke Testing
  E2E Testing
  Regression Testing
  Acceptance Testing
  Exploratory Testing
  Performance Testing
  Infrastructure Testing
  以上除接受测试和探索性测试之外,都应当是自动化的。
  Test Pyramid(软件测试金字塔)
  测试金字塔是Mike Cohn提出的概念,意思是测试从上到下-功能性测试到单元测试,数量由少到多,呈一个金字塔形。
  引自Martin Fowler的测试金字塔的文章,其实不用他说,写过测试的都知道,单元测试的成本低,运行
  速度快,但是到功能性测试,需要启动UI/浏览器去测试,成本很高,所以单元测试可以多考虑edge case,而
  功能性测试只考虑happy path。曾经参与过一个项目,基本上严格执行BDD的开发方式,用Cucumber写了大量的测试,
  但是每个产品的功能测试,运行完成都会超过一个小时,后只能通过切分测试,多加几个Jenkins Slave去并行运行,才将构建的时间降到半个小时左右。
  Unit Testing(单元测试)
  对于每个开发人员来说,对于单元测试来说应该都很熟悉。在我看来,写单元测试的好处在于:
  可重复自动运行,验证成本很小
  代码为文档,测试可以帮助理解程序或者业务逻辑
  单元测试一般粒度较小,因此发现问题,定位会比较快
  当然,也有人反对单元测试,认为大多数的单元测试都是废柴,比如这位,可能很多认为在代码或者逻辑不太改变的情况下,单元测试在运行之后没有什么太大的用处,但是一旦代码有任何调整,有单元测试是你重构的信心来源。
  单元测试做到什么粒度,也是有争议的,我司曾经提出过功能性单元测试(Functional Level Unit Testing),可以认为是单元测试粒度放大,针对具体的功能。
  单元测试的覆盖率应该是多少,也有争议,我当时的项目做到了后端代码测试覆盖率,js代码做到了70%-90%的测试覆盖率。有些人认为只针对关键功能,比如算法写单元测试可以了,80%
  是很不错得测试覆盖率了。不过,上次苹果的一个安全漏洞,是一段很简单的代码,如果有一个test case覆盖到的话,应该不会出问题了。
  单元测试的代码往往要多于产品代码,有时候维护也是个问题。
  TDD(测试驱动开发)
  提到单元测试不得不说一下TDD测试驱动开发了,测试驱动开发是在写实现代码之前先写测试,遵循下面的步骤:
  1.写测试,运行,失败(红)
  2.实现代码,运行测试,成功(绿)
  3.重构,运行测试,成功(绿)
  这也是TDD的两顶帽子,功能实现和重构,每次只能戴上其中一顶。
  上面的描述缺少了TDD实践中一个很重要的环节,tasking,也是将story分解成粒度更小的test case,然后划定实现的步骤,小步快跑。
  从实际的项目中来看,TDD对于理清需求是有帮助的,有助于业务实现的正确性,保证了测试覆盖率,而且一旦建立
  这种节奏,稳步向前带来的自信感非常好。
  目前TDD的使用在国内外也是有争议的,我觉得很大程度上和敏捷社区如何推动这个技术实践的态度有关系,过于
  强势,“不做TDD怎么怎么了”,很容易让人反感,说实话我也很反感这样。用不用TDD和你的业务成功与否没有
  直接的关联,加之TDD实际上降低了对程序员的要求(责任感>能力),所以切实实践过或者是大神们认为它有缺点
  是很正常的一件事情,不过水平一般且没有实际的体会不要附和了。
  去年这个话题炒得很热,一定程度上因为DHH(Rails框架的创始人)的一篇文章TDD is Dead,之后他和Uncle Bob以及我司的Martin Fowler展开了一系列的论战。这种论战过程有趣,结果没什么意思,因为大神都比较顽固,不容易放弃自己的想法,不过不论他们怎么说,"Long Live The Testing",这点是要承认的。
  我个人对于TDD,觉得还是要宽容些,保持责任心和实现对功能的测试更加重要,不用纠结于是不是T-DD出来的代码。
  Integration Testing(集成测试)
  集成测试是各个模块组装进行测试,比如A依赖B模块,作为单元测试可能我只需要Mock或者Stub,但是集成测试会输入合适数据,真实的让A去调用B服务,测试是否会满足期望的结果。
  现在微服务的这种架构方式很流行,一个简单的例子是前后端分离,前端与不同的微服务交互,如此部署,维护,
  扩展都会更加容易。在这样的架构下,集成测试是一个问题,Pact Testing是一个可选的方式。Pact是基于CDC - Consumer driven contracts的一个实现。
  我们可以将前端理解为Consumer,后端理解为Provider,完整的测试过程如下:
  Consumer提出要请求的Provider的API格式,传入的数据以及期望的返回,运行测试后生成pact(json)文件并将其push到Provider的repo中
  Provider CI运行Pact测试,因为API还没有实现,测试会失败
  Provider的开发人员根据Pact文件中约定的Restful API以及Input/Output数据,实现并且通过测试。
  目前Pact已经有多种语言的实现,比如ruby, Java/Scala等。
  Functional Testing
  功能测试,是对产品的功能进行验证。对于web项目来说,是启动服务,通过浏览器访问测试。曾经做过的Ruby
  项目是使用cucumber去进行测试,其实依赖的工具是Capybara和Webdriver/Selenium。讲到这里不得不提
  BDD - Behavior Driven Development了
  BDD
  BDD更关注从业务的角度出发,用Business Readable DSL的方式去描述一种实例行为,期望应用可以满足预期。
  Cucumber是其一种实现,其测试的基本的格式如下:
  Given I am a super user
  When I log in the website
  Then I should see "Admin" on the top right
  通过在每个step中写一些Assertion,可以判断功能是否实现等。
  我的第一个项目是严格遵循BDD的流程,基本顺序如下:
  在接到一个需求时,按照描述,先写Cucumber测试
  运行测试,失败,然后编写对应的单元测试
  运行单元测试用例失败,实现之并让其通过
  运行Cucumber测试,通过
  重复1-4直至完全满足需求
  做的好的话,整个开发的节奏会非常流畅。BDD原本期望是让BA或者产品经理去读或者写测试描述,但是很少有BA
  或者产品经理回去阅读这些测试,写更不可能了。程序员自己写的话,表达很难贴近正确的业务行为,加上不正确的
  添加测试(通常Cucumber测试只检查Happy Path),导致测试越来越多,运行越来越慢,所有测试跑完的时间,远超过
  我seed一些测试数据,启动浏览器手工测试快些。
  以前的团队有很好的BA,在Story的Acceptance Criteria中,会将用例写的很清晰,这样基本上不用怎么修改可以
  写好Cucumber测试的step,现在倡导全功能团队,BA和QA这两个Role几乎都没有了,客户也不怎么提BDD了……。
  Smoke Testing(冒烟测试)
  冒烟测试是确认软件的基本功能是否正常的测试。
  E2E Testing(端到端测试)
  端到端测试,可以理解为整个系统在现实世界中的走完一个完整流程的测试。领域不同,端到端测试的概念不尽相同。
  我们当时的端到端测试只cover了与其他系统的接入点功能的验证以及基本的流程的验证。因为要访问不同的服务,
  而这些服务的可用性不一定很高,同时在运行测试时需要保证其他的系统都使用了合适的版本,当时的E2E测试经常
  会出问题,修复也很麻烦,一定程度上得怪亚马逊EC2机器性能或者网络的问题-_-!
  Regression Testing(回归测试)
  回归测试,确保新加入的功能没有破坏已有的功能。讲到这里得介绍下,在持续交付的前提下,我们通常用两种方式:
  1.Feature Toggle
  2.Feature Branch
  用来保证实现新功能同时不影响旧功能。
  Feature Toggle译为特性开关,是通过配置文件/数据库来判断是否使用新特性的代码,其实是读入配置文件,然后用if/else去判断用新或者是旧的代码。
  这个的好处在于你可以只在主干上开发,保证不破坏现有的功能,同时还能保证持续交付/部署。要付出的代价是写针对toggle打开或者关闭情况下,
  两种状态不同功能的测试,如果有两个toggle,那么可能得针对四种状态写测试。在回归测试的时候会关闭所有Toggle,对应的还有Progression测试,
  既打开enable所有toggle,新功能的测试应该都通过。如此测试的数量会大大增加,流水线构建的速度也会随之减慢。
  Feature Branch是在新的Branch上开发新功能,坏处是与主干merge太晚,可能会出现merge hell,很难一下修复太多冲突。
  相比之下Feature Toggle要更好些,但是注意好不要加太多Toggle。
  Acceptance Testing(接收测试)
  是开发或者测试将实现好的功能,在测试环境中,给business people做show case,然后证明所有的需求都正确实现了。只有他们说OK,story卡才能认为是done了。
  Exploratory Testing(探索性测试)
  新产品上线前,找一群对产品完全不了解的人,让它们试用,以期发现一些诡异的bug或者使用不合理的地方。做
  过一次,还真的发现了bug。
  Performance Testing(性能测试)
  dark release 的时候,针对产品环境,使用典型的应用场景,进行性能测试,比如并发数等等。常用的工具有
  ab,Jmeter, Gattling等。Jmeter
  感觉配置比较麻烦,Gattling是基于Scala的,用起来还不错。
  Infrastructure Test(面向基础架构的测试)
  面向基础架构的测试是近年来提出的一种概念,应该上过我司的Tech Radar。
  随着DevOps运动的兴起,"Infrastructure as Code"概念深入人心, 既然“基础架构即代码”,那么针对
  这些代码,我们也可以做基本的单元测试等。比如puppet , chef。甚至有Babushka以及ServerSpec这样的为sysadmin准备的TDD工具。
  除了开发环节,对线上的产品环境也做测试, 比如:
  Chaos Monkey是NetFlix推出的帮助测试系统
  稳定性的工具,它会随机的干掉产品环境在AWS上一些节点,如果系统的弹性很好,那么它会自动恢复这些节点。
  这是一种防御性的测试,同时通过测试的反馈,逐步的改进产品的稳定性和弹性。
  Nagios是一个监控工具,它可以测试产品环境的应用的健康状况以及服务器的监控状况。
  用类似Gomez的工具去检测应用的可用性状况。
  针对第三方的平台,如CDN,通常都是得手工配置,可以通过写单独的测试来验证配置正确,如CDN服务商提供的一些特殊的Header或者返回码。
  那么问题来了
  如果产品环境出现一个bug,该如何处理?下面是我觉得一个不错的处理方式。
  定位问题
  根据bug的场景,写一个失败的测试
  修复这个测试
  打包/部署测试环境/接受测试/部署产品环境
  结论
  场景不同,测试的方式方案不尽相同,但如果能保证大程度的自动化,那将是极好的。