0619接口测试框架实战

课程价值

  • 了解接口测试框架的几种形态的适用场景

  • 掌握测试开发如何梳理需求和进行开发设计

  • 开发一个只需配置yaml文件就可以完成基本接口测试的测试框架。

大纲

  • 测试框架方案思考
  • 项目需求分析
  • 项目开发设计
  • 项目开发任务拆分及验收标准
  • 开发实现与测试

时长

300分钟

课前准备

  • 结合之前xunit部分的内容,创建一个Junit5+maven+Rest-assured项目,作为实战演练项目
    ##开发日志

开发日志

一、定义ApiActionModel,用来用来存储接口对象yaml反序列化出来的action单元

  • 1、确定请求方法和URL
  • 2、请求参数、URL中全局变量替换 PS:这里需要编写占位符工具类PlaceholderUtils 和全局变量类
  • 3、根据形参和实参构建内部变量Map
  • 4、请求参数、URL中内部变量替换
  • 5、拿到上面替换完成的接口请求数据,进行请求并返回结果

二、定义ApiObjectModel类,用来用来存储testcase yaml反序列化出来的ApiObject实体对象

*1、添加load方法,反序列化一个yaml文件为ApiObjectModel对象

三、定义ApiLoder类,用来加载api对象和获取某个action

*1、实现load方法,批量反序列化一个路径下所有的yaml文件。
*2、实现run方法,根据api名称和action名称从反序列化的对象列表中获取某个action。

四、定义StepModel类,用来用来存储testcase yaml反序列化出来的step单元

  • 1、需要定义AssertModel类,用来存储反序列化出来的断言对象
  • 2、需要定义StepResult类,用来存储请求的相应信息和断言结果
  • 3、替换实参中的变量
  • 4、根据case中的配置执行相应的action,当然要传入替换过变量的实参
  • 5、根据case中的配置截取响应中的字段,并存入step变量Map中
  • 6、根据case中的配置截取响应中的字段,并存入Global变量Map中
  • 7、根据case中的配置对返回结果进行软断言,但不会终结测试将断言结果存入断言结果列表中,在用例最后进行统一结果输出
  • 8、将response和断言结果存储到stepResult对象中并返回

五、定义ApiTestCaseModel类,用来用来存储testcase yaml反序列化出来的TestCase实体对象

  • 1、加载用例层关键字变量
  • 2、遍历执行所有step
  • 3、处理step返回的save变量
  • 4、处理assertList,到最后进行断言。
  • 5、进行assertAll断言

六、定义ApiTestCaseModel类,用来用来存储testcase yaml反序列化出来的TestCase实体对象

  • 1、遍历目录下所有的用例文件,并组装成参数列表

课后作业

image
请问下各位同学,老师,这里一个全局变量替换,一个内部变量替换有什么区别啊,有点没看明白,还有query请求所需要的各种参数对吧

0.0  确实有点绕,简单理解了一下,不对的地方请纠正我
1.首先在ApiActionModel有哪些会用到的字段总结一下:
 a. ArrayList<String> formalParam ->这个是存储参数的key(例如"corpid":"value"  中的"corpid")
 b. HashMap<String,String> query ->这个存储参数中的key,以及key的引用形式(例如"corpid","${corpid}")
 c. ArrayList<String> actualParameter  -> 这个存储的是实际的value,(例如"corpid":"value"  中的"value")
 d. HashMap<String,String> actionVariables  ->这个会在ApiActionModel中将formalParam和actualParameter进行拼接
	以a, c中的例子,最后转换后的actionVariables应该是("corpid":"value")
	
2.全局变量替换
a. 全局变量例如accessToken
	因为全局变量一直会用,所以都存储在 HashMap<String,String> globalVariables中-->这个字段其实和actionVariables意思差不多
	不管body/url/query中是否需要全局变量,都会去循环拿一次,如果key有对应的就替换,没有就不执行
		//body全局变量替换
		runBody = PlaceholderUtils.resolveString(runBody, GlobalVariables.getGlobalVariables());
		//url全局变量替换
		runUrl = PlaceholderUtils.resolveString(runUrl,GlobalVariables.getGlobalVariables());

b.内部变量例如body中的"name","enName"
	最后都存储在actionVariables中,
	//body全局变量替换
	runBody = PlaceholderUtils.resolveString(runBody, actionVariables);
	//url全局变量替换
	runUrl = PlaceholderUtils.resolveString(runUrl,actionVariables);
	
不管是全局还是内部,全局都是从其他地方拿到了,转换后存储在globalVariables中,内部是actionVariables
再去PlaceholderUtils去替换

多debug跑起来打断点理解更加清晰一点

全局变量:

在我们的实现中,可以简单的理解为全局变量可以跨文件引用,也就是说我在某一个yaml文件中执行了getToken的动作并且配置了
saveGlobal:
accesstoken: access_token
那么在后面不管在哪个文件中使用占位符${accesstoken}都可以取到正确的token值。

内部变量:

可以简单的理解为只有在当前yaml文件中才可以引用。内部变量适用于只在本文件内使用的变量

两种变量的使用场景:

比如在测试updateDepartment时需要前面createDepartment动作生成的departmentID时使用,如课上讨论,这种场景需要使用save进行内部变量保存。如果将departMentId错误的使用了全局变量,那么不同用例都存储相同变量时,会造成覆盖和混乱,所以在保存变量时需要区分场景。

变量替换

url/body/query这些可能带有占位符的信息,在使用前我们都需要进行占位符的扫描和替换,但是因为全局变量跟内部变量是分开存储的,这里看到了两次替换。

理解的没有问题 :+1:

谢谢阔海老师,和王好同学,我回头再理解一下

image
请问一下,红框中的变量参数,如何 编写这段的代码啊,老师

老师,可以给一下 这个框架的 图片上传的思路么?


可根据Rest-assured对上传文件接口的支持方法添加响应关键字即可,例如:
multipart: /path/to/file
在ApiActionModule的run方法中中进行解析和组装即可。

针对变量的替换,我们只支持了url和body、query,如果header中也需要引用变量,可以将header和query做相同的占位符替换处理,它俩都是hashmap,代码基本一致。

非常感谢,谢谢老师

感谢感谢,我去优化一下 run文件

老师,有几个实际面试中遇到的几个问题,想要请教你一下,希望能详细地解释一下,3Q
1.为啥使用这套框架;
2.你是怎么断言的,对哪些数据断言;
3.用这套框架,你有遇到什么问题吗,是怎么解决的;
4.你还知道 那些自动化的框架;
5.你有在需求阶段发现过什么问题吗;
6.自动化执行都发现了哪些bug
7.bug定位(老生常谈的问题,还是想知道应该从哪几个方面回答);

老师,请问下,

  1. 这里为什么 body 和 url 没有做非空的判断呢?我理解,在 resolveString() 和 resolveMap() 方法中都已经对入参的非空性作过校验了,这里其实不需要再做校验了?那么为什么 query 还要做呢?

  2. 如果 ApiTestCaseModel 中的 actualParameter以hashmap 形式传参的话,那么 ApiObjectMethodModel 中的 formalParam 以及这里的第3步“根据形参和实参构建内部变量map"就可以不需要了,个人认为这样的话更便于理解,同时在后续编写 ApiTestCaseModel 传参时,也更加不容易发生传参错误?

  3. PlaceholderUtils 中做参数替换的时候,最终输出的都是 String 类型,那么如果希望传其他类型的数据呢?

  4. 这里的 contentType 应该是属于 header 的一部分,为什么不默认直接在 headers 中被处理了,而是单独拿出来赋值呢?是有什么特别的意义吗?

1、课程开始的时候系统的论述了这个问题:
因为ApiObject由结构化文档yaml编写,给后续的自动生成用例和数据迁移提供了可能性。
使用者只需编写yaml文件就可以完成测试工作,不需要代码基础,极大的降低了门槛。
2、我们在yaml文件中定义了断言字段,每次执行完step会将断言信息收集,会在脚本执行后期,统一进行断言。
3、这套框架的缺点我们在课上的方案中也讲解了:
比代码编写方式,丧失了很多灵活性。(比如有一个新的场景需要框架开发者进行代码维护)
编辑yaml时,IDE只会对yaml格式进行正确性验证,如果对各种引用错误无法识别。(编写是容易因为拼写错误造成失败,并且没有代码那么容易定位问题)
4、httprunner/restassured/metershpere等比较流行的框架
5、其实做自动化工具,在需求阶段最大的问题是,一定要找真实场景去收集需求,并拿真实场景去验证你的需求设计,不要想当然的认为功能应该怎么做,不然容易做出来的工具并不能解决实际问题而前功尽弃。
6、自动化一般都是在回归测试中发现一些因为代码的改动引起的连通性问题,或者一些接口返回了非预期的结果。
7、BUG定位最直接的方式是查看日志,看是否有明显报错信息,如果是前端页面可以看下开发者模式的控制台,一般也会有相应的报错信息,另外也可以从数据库中对比正常数据寻找差异点,最终定位到问题的产生原因。

1、其实我们这里并不是严格意义上的非空校验,我们代码里判断字段是否为空其实只是为了分析用户填写的信息来判断是否需要执行某些逻辑,比如说如果headers不为空我们才执行头添加的动作,
再来分析URL没有判空的问题:url的赋值有两种情况,一种是使用get或者post直接设置,另外一种是在URL和method中分别设置,我们代码是通过判断post和get属性有没有设置来反向判断的URL设置情况。
body判空其实是在下面的代码中做的,
image
2、这个问题其实有点像我们定义方法的时候会指定形参,而不是每个方法都要求传一个map进来,否则调用方是不知道参数的key叫什么的。
3、其实传的值的类型不是由变量替换的类型决定的,而是跟引用时的写法有关系,举个例子:
在update中${departmentId}的变量引用应为两边没有加引号,在替换完后其实是以整形的方式提交的请求。

如果我在引用外层加上双引号,就变成字符串类型了:

4、contentType的这个方式是模仿了restAssured的语法习惯,因为contentType是很常用的一个头信息,单独封装一个方法可以减少用户拼写header的key名的麻烦。


这个我调试不成功哎

代码地址发来看下

老师,请问token存在headers里面的时候要怎么弄呢,试了一下现有的代码好像不能满足这个需求。。。。

参考query稍微改造了一下,已解决。