本系列其他文章:
第一部分:测试开发之路 ---- 框架中数据的管理策略
第二部分:测试开发之路 ---- 数据驱动及其变种
第三部分:测试开发之路 ---- 可读性,可维护性,可扩展性
第四部分:测试开发之路 ---- 可读性,可维护性,可扩展性 (续)
第一个要讲的是测试数据的管理,因为在我以往的公司中,我发现这方面是大家最容易忽略,但却是影响成败的最关键的几个因素之一。
说到测试数据,我想大家第一个反应就是数据库。现如今大多数产品,尤其是互联网产品都是使用数据库来保存数据的。以下的例子中,我均已数据库为例。那么在测试中,测试数据的准备和维护就成了一个很重要的方面。同样的,自动化测试中,测试数据的管理也成为了一个难点。管理不好,你根本没法自动化,因为你的脚本不能够重复执行,注意重复执行的重要性。不能重复执行的脚本,不配称为自动化。 也是不能加入持续集成流程中的。这也是我痛恨的那些为了 KPI 而被创造出来的,高大上的,“所谓的测试平台” 的特性。
好了,既然提到了测试数据,那么我们先把测试数据分个类吧。
OK,貌似我们看到了使用共享数据的方式除了速度快以外貌似没什么出众的地方了,而且缺点貌似也很严重,大家会不会想,那么我们就不要用这种方式了好不好? 其实也是不对的,只要使用得当,这种方式在某些场景下也是比较好的方案。具体我后面再说
好吧我说了太多这种方式的缺点,因为我曾经是真的被坑的很惨。 所以我是不建议用这种方式的。
好了这种分类我也说完了。可以看出我是比较偏向使用 sql 创造数据的。第一种分类的时候虽然我说共享数据比较坑,但是在某些场景中还是很有用的。但是第二种分类中,我十分的强烈的不建议使用调用开发接口创造数据的方式。case 一旦多了,一个 bug 就能让你崩溃。想象一下如果 case 库里有 5 千条脚本,一个 bug 搞出了 100 以上的脚本 fail 是什么感觉吧(我们曾经是 7 千,迭代一开始,就得专门找俩人维护脚本,什么也不干)。
OK,说完了两种分类,那么我们说说如何在我们的测试中使用这些数据管理方式吧。想了想,还是以我现在给我们公司写的这个框架举例子好了。
其实我是实在不知道该怎么给这种方式起名了,所以借用了 xunit test partten 一书中的定义。而且我也实在不知道 in-line 方式翻译成中文该怎么说,所以就这么叫它吧。它的使用方式很简单,也很原始。 你直接在脚本中,或者在各种框架中的 setup 方法,teardown 方法中直接写代码创建这些数据。例如 testng 中就有 beforeMethod,beforeClass 以及 beforeSuit 等标签帮你初始化测试环境。你可以使用原始的 JDBC 语句,也可以使用例如 mybatis 这一类的 ORM 框架。强烈建议用 ORM,配置个级联操作直接给脚本调用就行了。在脚本里写 sql 简直是噩梦。
这个方式不太好理解,什么叫注册式呢?就是你把测试数据注册到框架中,并说明是共享数据,还是隔离数据。这些数据的作用域是什么。接下来的事情就交给框架做了,框架帮你在测试执行前创建测试数据,测试结束后销毁数据。完全不用测试人员关心。这是我比较推崇的方式,我在接口测试中使用的就是注册式加 in-line 方式管理数据的,说明一下,用的是直接写 sql 的方式入库的,只不过 sql 由框架生成,我们不用管。 好了,这么说实在有点难懂。 让我们来看一下例子吧。
看过我在 Tester Home 的第一篇帖子的人应该对注册式数据管理有印象。 没看过的可以看一下,下面是链接:
NO_CODE 接口测试框架
在我的框架中,我创建了一个叫做 DataBaseFile 的注解(注解为 java 语言特性,非 java 工程师请 google),注解里定义了两个属性,一个叫 filePath。规定了数据文件的路径,这个数据文件做什么的呢?看下面截图。
这是一个 excel 文件,大家看这个是不是很像数据库的表结构。 答案是对了,其实这个文件就是从 navicat for mysql 导出来的。我们的思路是在 UI 上创建数据,然后用像 navicat 这种数据库客户端导出一个 excel 文件。框架会读取这个文件拼出三种 sql 并封装在一个对象里。这三种 sql 分别是 select,insert,delete。这三种 sql 就可以用来维护我们的测试数据了。这样就解决了测试人员写 sql 的问题。
OK,让我们看看 DataBaseFile 注解的第二个属性 ---- 作用域。这个属性是一个枚举类型,这个枚举暂时有两种值,method 和 class。其实这个属性规定了测试数据的作用域。如果测试类指定了 method 作用域,那么这个数据就是 “隔离数据”,框架会在每一个测试用例执行前创建数据,测试结束后删除数据。如果指定了 class 作用域,那么框架会在这个类的所有测试方法开始前创建数据,所有的测试结束后销毁数据。其实这就是 “共享数据” 了,测试类中所有的 case 共享这些数据。 有些时候这个策略是比较有用的。 比如被测功能不会对数据库造成影响的情况,例如查询某些订单的功能。测试这个功能的一批用例,就是可以使用同样的数据的,只不过可能我这个用例是按 id 查,下个用例按 name 查,其他的可能对查询结果做正序排序或者倒叙排序。所以我在一开始也说共享数据在某些情况下也是蛮有用的。其实还应该有个 suit 作用域,只不过我暂时没用上。
OK,知道了这些是不是就比较好设计实现了。 只需要在 testng 的各种 before 方法上做手脚就可以了。 具体思路就是创建一个基类,在基类里维护一个 List,list 里装的就是读取 excel 文件后分析出的三种语句的集合。在 before 和 after 方法里使用 java 反射技术读取子类的注解。这样就可以控制子类的测试行为了。 是不是还不算复杂?
抱歉现在在丈母娘家,用的媳妇电脑。源代码在公司的电脑里呢,51 过后我把具体实现贴上来吧。
这种模式是用来销毁测试数据的不二神器,专门在单元测试和小型集成测试中用来销毁测试数据的。 不过此模式依赖软件设计支持这种模式。也是就是大家说的需要软件有可测试性。具体怎么搞呢,就是要求开发在设计的时候就把测试考虑进去,不能在持久化层就把事务写死在被测方法里。而是把事务专门提取出一层来,或者使用 spring 这种提供了事务管理的框架管理软件的事务。这样的话,我们就可以在测试脚本中显示的控制事务。 在测试结束的时候,直接一个 roll back 就搞定了。 管你被测功能到底对数据库做了什么,我不 care~,直接暴力的全部回滚。 注册式数据管理方式的缺点就是它无法删除被测功能本身创造出来的数据。需要使用 in-line 的方式显示的删除这部分数据。 但是 transaction roll back 就方便多了。我心中完美的管理方式其实就是注册式加上 transaction roll back。 所以现在我们都要求开发再设计之初就考虑到这方面的事。 这也是方便他们做单元测试。 可惜这种方式不能用在接口测试和 UI 自动化中。
之后我会在可测试性那一章中,专门提及 transaction roll back 的,等不及的同学可以自行 google 这种模式。它出自 xunit test pattern。
OK,今天先说到这吧,媳妇叫我了。 以后如果有补充的我在修改帖子。 感谢大家的阅读。
Access 层的测试还要处理 cache 的 evict 这个怎么处理呢。。
#1 楼 @stephen753 以前我们对待缓存和索引。就是在数据库中插入什么数据,就调用相应的 http 接口往缓存里插入。其实就是直接操控缓存了
可以用 markdown 修改下帖子,更有结构性看起来会更好点~
#6 楼 @sigma 注册式的缺点确实是无法删除被测功能产生的数据。所以接口测试我们都是注册式加上 inline 结合着搞。最好还是不要依赖产品的接口造数据,尤其是共享数据。这么做确实会产生很多的 excel 文件。不过你可以考虑一下把 excel 文件也重复利用。例如造订单的 excel 文件做一个通用的。造用户的 excel 造一个通用的。商家的 excel 造一个通用的。很多 case 都可以使用这些 excel 文件。但是这些文件你仍然做成隔离数据。就是虽然很多 case 都用这几个文件。但是这几个文件的作用域仍然是 method。一个 case 运行前创建。运行后销毁。好处就是这些数据原则上都是隔离数据。我们也不用创建那么多的 excel 了。问题就是这些数据的内容是一样的。所有的 case 都引用这个数据。所以要求这部分数据必须稳定。不能经常变化。
#7 楼 @ycwdaaaa
1、对于通用的制造数据 excel,我理解是相当于 beforeMethod 中都要运行一遍 sheet=insert*,testMethod 中运行 sheet=select or businesslogic,afterMethod 运行 sheet=delete*,那么 beforeMethod 中需要写一些逻辑了,而且还是类参数化的那种(目前我是 DataProviderClass 直接 Iterator 提供给@Test的 method),你这样做灵活度确实高
2、对于删除数据,我目前直接使用 preHandler 和 postHandler 调用 resources 下的 sqlscript...,感觉做成你说的那个还需要完善一下我的 excelUtil,因为目前只支持单 sheet,你那个相当于多 sheet 遍历根据 sheetName 做判断,定义好格式然后去按照你定义的规则去编写 excel 中的数据,确实比手动写 sql 要方便也不容易出错
3、对于 [不要依赖产品的接口造数据],这个比如这样打个比方:
用户下了个订单,那么订单编号是自动生成的,如果不在运行时获取,预制数据的时候是不知道订单编号的,然后下一个接口的入参就是生成订单接口返回的 json 中的 orderId,当然也有可能有 7/10 个返回值下个接口用到 5/7 这种情况
你先搞清楚是测试结束后销毁数据还是测试执行前销毁
测试环境在虚拟机上,所以销毁数据用的是快照
有一个干净环境的快照vagrant snapshot save init
每次测试流程都是恢复到 init ,执行测试,测试完保存一个用于查错的快照
vagrant snapshot restore init
begin test
vagrant snapshot save $tag
或者用 docker
docker run --name api-test init
begin test
docker commit api-test init:$tag
#15 楼 @sanlengjingvv 好思路。docker 起容器很快的。就是我没试过有多快。不知道每运行一条脚本就恢复一下数据库是不是成本太高了。要用多长时间?我回去可以试一下。目前我们数据库没有单独运行在一个容器里。多谢你的思路
你碰到很多用例之间冲突的问题不能用数据隔离解决吗?
启动一个容器 1 秒左右,没试过每条脚本启动一个这么频繁的情况下会怎么样
#17 楼 @sanlengjingvv 数据隔离指的是?我遇到的有些场景。不删除数据是不行的。例如有的接口是列出所有的 data。另一个接口是创建一个 data。创建接口生成的数据就会影响到列出 data 的接口。所以我不太知道怎么给这种情况做数据隔离。我只能删掉它
说明你理论知识尚可,实战经验不足啊,关于如何删除数据,什么时候删除数据,好好琢磨解决方案吧
#25 楼 @quqing 我的观点是不需要保留数据的。因为这世界上有种东西叫 debug。首先如果测试脚本失败了。作为测试人员肯定要去分析一下原因。为什么失败了,环境错误?网络问题?case 之间互相影响了?还是脚本本身的 bug?起码要 debug 一下看看到底怎么回事对吧?如果这件事无法达成共识那么也不用聊了。如果最后排出这些原因。怀疑是开发的问题,那么分析是哪部分代码出的问题。UI 自动化失败的话那就看一下对应的接口测试报告。确认是前端的 bug 还是 server 的 bug。去代码库看看相应路径,是不是有代码变化。什么变化引起的 bug。自己分析下原因后发现是自己搞不定的才提交给开发。bug 管理工具中提出完整的重现步骤。这是一个测试开发起码要做的。这个可以达成共识对吧。
那么再说开发追踪 bug 的问题。如果你们是异步合作模式,开发去 bug 管理工具上获得详细的重现步骤。如果根据步骤不能重现问题,有可能是测试没写好。也有可能是双方环境不一致。如果开发一定要在测试环境中看一下测试结果。根据之前测试人员 debug 的结果。会没有数据保留么?debug 的时候在数据清除之前就打断很难么?就算测试人员忘了保留数据。我们都是自动化的,跑一边脚本几秒钟的事很难么。所以我们需要在框架里特意的保存数据么?
举个例子,批量跑接口测试,有些调用的接口会插入数据,结果某些插入的数据是有问题的,按照这篇文章的观点,运行结束就删掉数据,可能会出现以下场景:
开发问:我要看结果,持久化的数据到底是什么样的
测试说:测试数据跑完就自动删了
开发说:那我怎么知道这不是误报,进度这么紧,我可不想把时间浪费在排查误报的问题上
测试说:因为这世界上有种东西叫 debug
开发问:为什么 debug
测试回答:因为测试数据跑完就删了
开发问:那为什么跑完就删
测试回答:因为这世界上有种东西叫 debug
开发无奈:这是咋回事?
在每个 release 版本 cut 的时候做一个数据库快照,可以解决证据和清理的问题,当然这需要 DBA 同学们的协助
#40 楼 @1875884881 看到今天的回帖,感觉测试行业整体水平的提升,路,还很长。
这么说吧,日志只是一种参考。我相信有经验的业务测试,在执行案例时,至少但不仅限于打开 2 个工具,数据库的客户端工具、日志查看工具等等。对于业务数据落地的正确性而言,日志也许会提供一些线索,到数据库验证却是最直接有效的证据。如果你是开发,日志和数据库里的落地数据,你更相信哪个?更何况是测试工具打印的而非程序本身打印的日志。。。
我之前做性能测试时,每次任务开始,都是用存储过程,初始化一遍数据库。但是现在做接口测试,由于规模很小,所以直接在 setup 里面搞了。。。
很不错的贴,顶一下!
#56 楼 @1875884881 对有些人说的话别太认真
写的很有深度,学习了
感觉上面讨论的兄弟们都混淆了。。不知道说的对不对,抛下观点.
第一,测试开发看的是 log,log 报错的是源点,是数据错了还是脚本错了还是接口反馈的原因,这些日志都看的出来,而作为测试数据的确实 rollback 毫无影响。
第二,测试的角度是这样的,我看到了数据错误,那么我拉开发来看的时候,这个时候是需要一个证据来证明此处有错的。有的开发脾气人好好说话,有的根本不想鸟你,打个比喻报了个数据库 insert table 时 data too long 的错误,你说要改成 vachar(500),脾气不好的就会觉得 300 就够了,为什么要 500 那么长,而且可能有的字段还是 index. 这个时候,保留下来的证据 (数据) 就显得很重要了!!
一点小观点,轻拍,感谢!
毕竟大家测试这一块 业务不同,工作性质不同,看待东西的角度也是不同的.
还有,楼主好文,谢谢分享
一周的工作忙完了,静静的喝杯茶再来读读这个系列,看能否有所收获
思路很好,可以拜读下源码吗?让我等菜鸟学习一下实现可好?
赞! 问一下,数据库插数据的话,我们这边线上不给数据库操作权限,咋整?你们这边是有权限的还是有其他方案。。。
#76 楼 @phicomm123 为啥要搞线上数据库。。。
@ycwdaaaa 比如要跑查询订单的接口 那不是先要在数据库里造订单数据吗。。。
#78 楼 @phicomm123 恩,那不一定非要到线上搞数据下来。。。
@ycwdaaaa 那咋整 求教。。。
#80 楼 @phicomm123 可以自己造啊。。。。或者找 DBA 把线上数据脱敏后 dump 下来。。。。
@ycwdaaaa 明白了 谢谢
@ycwdaaaa 请问下你们测试用的请求数据和预期结果数据是手工准备维护的,还是自动生成的,特别是预期结果怎么自动生成
博主好,对于非常复杂的业务,设计到主从数据库、redis、hbase,还有同步服务等等很多个数据库才能完成一个场景,这种隔离数据成本会不会比较大。
16 年的帖子现在才有幸看到,博主提到的接口产生的数据,使用 in line 方式删除。那如果接口影响的是原本的已有的数据呢,比如说修改密码:修改密码接口的参数有账号、原始密码、新密码,例:原始密码 123456、新密码是 12345678,第一次执行该接口没问题,顺利通过,那我下次是如何能够还是使用原始密码 “123456” 就通过修改密码的接口呢?不知道你说的 in-line 方式能不能达到还原数据的作用。
时隔这么长时间我也有点忘了哈, 在 inline 里你可以写 sql 去做删除和更改,你想恢复什么样的数据自己写 sql 就好了。 不过我现在的做法是用 assertJ-DB。 监控数据的变化,然后自动的把数据恢复到之前的状态。 你可以看看我之前还写过一个文章。 说 AssertJ-DB 的
今天学习了您的文章,感觉您这个框架主要针对的是手工接口测试人员的优化,在接口测试阶段,接口测试人员可以利用这个框架,将测试数据准备到 xml 文件中,批量的利用这个框架来执行自己的测试用例,您看我这么理解对吗?@ycwdaaaa
@ycwdaaaa 求源码 膜拜一下