# 19|排兵布阵:自动化测试的编排、扩展与重构 你好,我是柳胜。 通过前面两讲,我们一起推导了一个自动化测试Job元数据模型。接下来,我不但会把这个Job模型用到自动化测试设计里,还要“点石成金”,让你充分体会到这个模型的强大力量。 在讲解设计过程之前,我想先和你聊聊,现在的自动化测试设计有什么问题。 如果你做过自动化测试开发,对现有设计一定不陌生。通常做法是创建一个TestSuite,它包含多个TestCase,每个TestCase完成一个测试任务,然后顺序跑下来,测试就执行完了。就像下面这样。 ![图片](https://static001.geekbang.org/resource/image/c5/5f/c5c1d2c97ea26bb8a1d07724aa5b515f.jpg?wh=1920x962 "传统做法") 我相信,很多自动化测试开发人员刚入行的时候,就是这么学习和组织自动化测试案例的,一直到现在。 但你有没有想过,这种测试案例的组织方法是来源于单元测试。它的方法论是,有一个开发方法,就需要有一个对应的Test方法,两者一一对应。因为先有开发的代码存在,那么Test方法有多少个,都负责干什么事,这些都一眼到底,几乎不需要测试设计,往Test方法里填充代码就可以了。 这种自动化测试开发方法,我叫它**“轻设计,重实现”**的方法。不过,这种方法反过来也会潜移默化地影响自动化测试人员的思维方式。不想好自动化测试的设计,上来就先写代码,结果代码写出来又长又冗余。 而自动化测试已经发展了这么多年,早就不再局限于开发阶段的单元测试了,有API测试、UI测试等等。这些高层面的自动化测试案例,其设计往往以功能为入手点,对业务进行分解,进行自动化建模。在这个过程中,就不能像单元测试那样,有现成的开发方法做对标和参考了。 而在这样的背景下,自动化测试案例的设计,逐渐成长为一个非常重要的环节。所以,我们应该要走向“**重设计,轻实现**”的自动化测试开发方法。 所谓**重设计**,其实就是我们要像开发人员一样,去设计自动化测试案例,要考虑后面这三大关键问题: 1.概要设计。要写多少个TestJob,它们之间关系是什么? 2.详细设计。每个TestJob该怎么实现? 3.需要扩展和重构怎么办? ## 概要设计:要写多少个Test Job? 我们先从概要设计开始梳理。一说概要设计,你是不是马上想到的问题就是:要写多少个Test? 但其实这个问题无法直接解答。因为刚开始规划一个软件系统的自动化测试,我们只知道一个大概的场景需求,并不会一下子就知道,需要开发多少个Test Job。 比如下单功能,它是从Web UI下单,然后用户通过快递得到食物。为了去测试它,需要多少个Test Job?做多少个检查点?这些问题,需要从概要设计中梳理出答案。 那概要设计怎么做?一步步来。 第一步,我们先把大概的自动化测试需求梳理出来,建模成为测试Job,这里我们把测试Job模型再“召唤出来”,一一去对号入座。 ![图片](https://static001.geekbang.org/resource/image/e6/f7/e6de0d6d56ea556f8584de2353e51ff7.jpg?wh=1920x1310 "测试Job模型") ### 先完成第一个根Job Job的名字是“UserPlaceOrder”。Job的Input是什么呢?要想启动Job,需要有一个实例的URL,还有登录所需的User Name和Password。 接着往下分析,Job的Output是什么呢?用户得到食物。那用户得到食物的标准是什么?因为物流是Foodcome的第三方服务,FoodCome处理完食物的标准是:生成物流单号,并且用户收到了“食物快递发出”通知短信。那我们UserPlaceOrder的Job Output就是DeliveryTicketNo和DeliveryNotificationMessage两个信息。 Job的TestConfig是什么呢?这里我们需要考虑的一个Job参数就是Timeout。 从自动化测试的执行效率角度看,每一个Test Job都应该承诺在一定时间内完成,这样自动化测试的运行时间才能可控可预期。另外,从业务角度,每个业务都有[SLA](https://zh.wikipedia.org/wiki/%E6%9C%8D%E5%8A%A1%E7%BA%A7%E5%88%AB%E5%8D%8F%E8%AE%AE)的时间要求。 设想一下,我们对客户承诺,处理一个订餐的单子最长时间是30分钟,那么对应在Test Job的实现中,30分钟后,如果还没有输出DeliveryTicketNo和DeliveryNotificationMessage这两个信息,客户的承诺没有达到,Job应该就会失败。所以,我们在TestConfig里设定Timeout=30mins。 现在,UserPlaceOrder的Job我们就梳理出来了,画成六边形是这个样子: ![图片](https://static001.geekbang.org/resource/image/99/53/999031947e107324d4cbb617e3bedb53.jpg?wh=1920x728) 它的XML表达是这样一个文档: ```xml LoginURL UserName Password DeliveryTicketNo DeliveryNotifyMsg Timeout:30mins ``` 这个Job写出来后,你就完成了UserPlaceOrder这个Job的概要设计,也相当于开发完成了一个小型系统的概要设计。 对于开发来说,系统用一个模块就能完成,还是由多个微服务共同完成,这取决于业务复杂程度和实现能力。 那对于自动化测试设计来说,这个UserPlaceOrder是直接就可以开发了,还是需要继续细化成子Job,怎么决策呢?我们可以从**复用性**和**实现能力**两个角度,来决定是否拆分。 ### 根据复用性进行拆分 先从复用的角度来看,登录是所有功能的前置条件,如果我们把登录测试任务提炼出来,是可以提高ROI的。按照这个思路,我们把UserPlaceOrder Job拆分成Login和PlaceOrder两个Job,像这样。 ![图片](https://static001.geekbang.org/resource/image/e2/3a/e205cc2ff8d14d448a9a5eb40e25d33a.jpg?wh=1920x938) ### 根据实现能力拆分 再琢磨一下,PlaceOrder这个Job包含了创建订单和验证订单两块逻辑。创建订单从Web UI上完成,而快递单号需要从数据库里查到,但是短信要从短信网关上得到,所以DeliveryTicketNo和DeliveryNotifMsg是两个技术上独立的实现的方法,它们应该被分割成两个Job,每个实现用不同的技术,这也遵循了**面向对象设计单一职责的SRP原则**。 ![图片](https://static001.geekbang.org/resource/image/c1/25/c10b0f0e81ec2fb8da9ebbyy0b4f7f25.jpg?wh=1920x1425) 在分解Job的时候,子Job要实现父Job的对外承诺,也就是Input和Output。而且在运行的时候,一个父Job的所有子Job都太运行通过,父Job才能通过。 这就是我们要创建一个VerifyOrder抽象Job的原因。因为它下面的2个子Job,即快递单号和通知短信都要成功,verifyOrder才算成功。 同样的道理,createOrder和verifyOrder都要成功,它们的父Job placeOrder才算成功。这样,我们的Job设计就咬合在一起了。 讲到这里,我们再回顾开头那个问题:要写多少个Test Job。是不是就有答案了? 作为测试架构师,或者自动化测试设计人员,你的首要任务是完成抽象Job的设计,把Input、Output、Testconfig、Testdata先定义好。之后要继续抽象再做拆分,还是按现在定义的Job实现接口,这个要交给你的自动化测试团队来决定。你其实不需要过多关心他们是怎么实现的,又用了多少个Job。 但是我们要注意, **Job的拆分是有一个度,太大了,将来维护和诊断就会很费力气**。你想想诊断一个1000行的代码模块和一个100代码模块,哪个更容易,就能明白了。拆分得太细了,就会让复杂度升高。 拆不拆分,你要遵循这两大原则:第一,提高复用性,也就是ROI;第二,方便独立实现和维护。 ## 详细设计:Test Job怎么实现? 可能你注意到了,设计Job的过程,在思维上是一个笼统的想法逐渐细化的过程,反映在Job模型上,就是从一个Job生长成一棵Job树的过程。 你可以看看后面这张图,它能形象地描述这个过程。 ![图片](https://static001.geekbang.org/resource/image/34/4b/34494bb8f326ca0625c5f5661yyeda4b.jpg?wh=1920x954) 设计完成后,这个Job树的每一个叶子,就是我们概要设计的成果,它是我们最终细化到可开发的实例Job,也就是传统的Test Case了。 这些实例Job,我们还要决定怎么来实现,放在金字塔的哪个层面做测试,具体用什么工具框架,详细设计做完之后,就能进入具体实现环节了。 按照第一模块讲的3KU法则(可以回顾[第二讲](https://time.geekbang.org/column/article/497405)),我们就可以确定每个Job要在金字塔的哪一层来完成。确定了这一点,工具和框架自然也就可以确定了。 对于我们的订餐系统,Login放在API层面,使用RestAssure框架;CreateOrder在API层面,使用RestAssure框架;DeliveryTicketNoVerify在DB层面,使用JDBC;DeliveryNotifMsg在SMS gateway层面,使用Java或Python。 我在六边形下面加了一个紫色的方块,来代表实现方法。 ![图片](https://static001.geekbang.org/resource/image/1e/c4/1e5c03d07cf625f9e5fefa96deaaeac4.jpg?wh=1920x1291) 到这里,我们就可以进入到实现阶段了。自动化测试Job就是一个个具体的模块,有输入、有输出、有实现方法,足以指导自动化测试开发人员去开发代码了。 而负责Job开发的自动化测试开发人员,并不需要了解整个Job蓝图,只要他按照接口去做,就可以把实体Job组装到场景里。这个跟开发的思维是一样的,每个开发人员遵循详细设计开发一个个模块,最后整个系统就会运转起来,也就是**模块化开发**。 ## 扩展和重构 设计做得好不好,还要考察扩展和重构的能力。所以,我们要看一下,Job模型是怎么支持扩展和重构的。 ### 扩展 假设未来有一天,我们发现前面的设计方案有瑕疵:比如,这个工作流没有走UI层面,没有验证UI上的功能表现。那我们就想加一个verifyOrderOnWebUI来验证订单,那怎么办呢? 我们还是先梳理verifyOrderOnWebUI的Input和Output,它所需要的Input用户名、订单号这些信息,是否已经存在,又是由哪个Job 输出的。明确了这些,只需要把verifyOrderOnWebUI的Depdency指向它即可。其实意思就是,前面的Job运行完了,有了这些信息了,verifyOrderWebUI就可以运行。 在这个具体的场景里,我们把verifyOrderOnUI就作为verifyOrder下的一个子Job,可以复用verifyOrder的Depdency和Input。 ![图片](https://static001.geekbang.org/resource/image/e0/82/e07be8a9204f4b27d63cd3a8e7331a82.jpg?wh=1920x1019) ### 重构 跟软件开发一样,自动化测试也需要修改、重构。在传统的自动化测试代码里,没有TestJob这个概念,往往是一个工具下面带着一群TestCase代码。 TestCase之间的依赖和数据交互这些逻辑会存在隐式的耦合,一换工具,全部的Test Case都要重新写,即使重写其中一个Test Case,也要倍加小心,说不定就会影响到其它的TestCase。 而有了TestJob之后,每一个TestJob都有清楚的接口,可以有自己独立的实现方法。这也符合面向对象设计中的单一职责SRP原则和开放-封闭OCP原则。我们可以把修改的影响,控制在TestJob的范围内。 ![图片](https://static001.geekbang.org/resource/image/13/db/13c987dab72847032afcee3aa1460cdb.jpg?wh=1920x1528) 这个修改可以是代码的修改,也可以是工具的替换。比如,有一天我们发现verifyOrderOnUI的web测试工具Selenium不好用了,想替换成Nightwatch,怎么办?很简单,这个替换仅限于这个Job,所以用Nightwatch重写一遍,只要保证Input和Output不变,就是对外承诺不变,我们心里就有底了。 ## 小结 为了让你理解透彻,我再用类比的方式,为你概括一下微测试Job理论:一个微测试Job,就好比开发里的一个微服务,它是有独立的接口,配置和实现方法。运行这个微Job就是完成了测试任务,它会有自己的状态,通过、失败还是无法运行;同时,它会输出自己获得的数据,供其他Job使用。 我为你准备了一张图,帮你快速回顾微Job模型的设计流程,你可以保存下来做个参考。 ![图片](https://static001.geekbang.org/resource/image/f1/35/f1273a8e298e6753dddbca1341243735.jpg?wh=1920x1080) 在我看来,这个微Job模型是一种新的设计思维。它给我们带来好处是,让自动化测试有了**设计先行**的理论基础,而且能够分解复杂的测试场景,降低和工具框架的耦合。 但这也带来了新的问题,虽然方法论上站住了,但落地的时候会有挑战,因为Job会比以前切得更碎,Job模型转成自动化测试案例的时候,这最后一公里路应该怎么走?下一讲我们再继续。 ## **思考题** 你的手工测试案例是怎么设计的呢?想想能不能也用Job模型来设计呢? 欢迎你在留言区跟我交流讨论,也推荐你把这一讲分享给更多朋友。