You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

238 lines
15 KiB
Markdown

2 years ago
# 05Auto Gen Auto所有测试工作即代码
你好,我是柳胜。
我们前面用了4讲篇幅讨论ROI模型和由此衍生出来的一套实践原则从分层测试、选型思路和具体代码多个角度探索提升ROI的方法。
这些方法还都是基于常规的自动化测试开发流程,先有测试需求,再设计测试案例,然后做自动化。以登录测试为例,我画了一张流程图做说明。
![图片](https://static001.geekbang.org/resource/image/e5/82/e524642fdd13d88e63de2900325f5e82.jpg?wh=1920x462)
自动化测试的开发成本,就是把测试需求转变成自动化测试代码这个过程花费的时间。在我们的图里,它是从左向右,所以我管它叫做**水平开发成本**。
![图片](https://static001.geekbang.org/resource/image/5f/98/5fcyy4827fff8970ef6dc39aeb0ca598.jpg?wh=1920x709)
当登录功能测试需求发生变化时,就会重新走一遍这个流程,出现了多个版本的测试需求,也会带来多个版本的自动化测试案例。从下图可见,这个版本是自上向下增加,所以我管它叫做**垂直维护成本**。
![图片](https://static001.geekbang.org/resource/image/ab/e4/ab257d846526c1e731da9812c9cd08e4.jpg?wh=1920x781)
我们现在可以直观地看到开发成本和维护成本了。好,问题来了,有没有办法**从流程上动手术,来降低这两个成本呢**
这就是我们今天要讲的Automation Generate Automation也叫自动化产生自动化测试代码为了方便起见下面的篇幅用缩写Auto Gen Auto来指代。
## Auto Gen Auto 技术
常规的自动化测试是指用代码实现设计好的TestCase而Auto Gen Auto的目的是让Test Case生成也自动化如下图所示。
![图片](https://static001.geekbang.org/resource/image/b9/b0/b9650d372e55704a31431faa8f4cb6b0.jpg?wh=1920x770)
因为从测试需求到自动化测试案例是完全自动化的每次需求改变的时候只需运行一次Auto Gen Auto即可生成新的自动化案例垂直维护成本为零。所以Auto Gen Auto技术如果能落地ROI就会大大提高。
### 从何处下手
那Auto Gen Auto用在哪性价比更高呢
业界熟知的测试方法是黑盒测试和白盒测试。白盒测试从测试案例设计开始,需要我们先了解代码逻辑结果,一个函数里有几个判断分支,处理那些数据。基于这些了解,再设计案例验证函数输出和达成代码覆盖率。
在白盒测试里Auto Gen Auto不是啥稀奇事XUnit框架都提供了不少开发IDE的plugin可以扫描一个class的函数直接产生test方法。开发人员只需补充少量代码test方法就可以运转起来了。
与之对应的是黑盒测试,测试案例设计不基于产品代码,而是用户规格说明。比如,用户在订餐系统上完成一个订单,用户该怎么操作,下单成功后应该收到物流单号等等,设计这些测试案例的目的是验证业务能够完成,不需要去看代码。
今天,我们要关注的是**在黑盒测试领域的Auto Gen Auto**,这个更有挑战性,也更有探索价值。因为,作为测试人员花了大量时间来设计黑盒测试案例,而且还要手工维护这些测试案例的变化,这个过程要是都能自动化了,就会省去很大的重复又枯燥的工作量。
### 如何实现
怎么做到Auto Gen Auto呢用代码生成代码前提是测试需求得有一定的规则或模式然后代码才能解析规则根据规则生成最终的测试代码。
这个实现思路在开发中是很常用的比如Maven Archetype使用模版自动生成项目代码Soap使用WSDL来生成调用桩等等原理图如下。
![图片](https://static001.geekbang.org/resource/image/0f/cd/0f556ba335e4fef6286140819d804acd.jpg?wh=1920x901)
所以要做Auto Gen Auto我们的目标是先要找出测试需求里的这些规则并把它们表达出来放在一个规则文件里。我们看看下面的例子。
## 测试等价类的规则
远在天边近在眼前我们在测试案例设计中经常用到的等价类和边价值方法就可以作为Auto Gen Auto的规则。
等价类是指某个输入域的子集合,在同一个子集合里的所有元素对于测试的效果都是等价的。
我们要测试一个订餐系统的用户名首先要了解用户名上的约束。从长度上来看假设用户名最大长度是255个字节根据这个约束至少能产生2个测试等价类有效等价类是小于255字节的用户名无效等价类是大于255字节的用户名。测试用户注册功能时就可以用到这2个等价类了。
用同样的思路看用户名的另外一个约束那就是字符类型的限制假设用户名只能由英文字母和数字组成根据这个约束又可以产生多个等价类中文字符、ASCII字符、数字、High ASCII等等。
看到没有?其实我们用等价类方法设计测试案例时,遵循的是**等价类划分规则,设计出来的测试案例也与等价类一一对应**。但手工做这些,工作量会很大,因为整理约束时会有遗漏,改变约束的时候,也容易忘了维护测试案例。
如果能让测试案例和等价类自动对应然后依据规则动态产生测试案例这些问题就会迎刃而解。不过我们得先把这些约束规则外化表达出来在这里我用一个user-rule.yaml文件来表达这些规则。
```yaml
name: user name rules
appliedTestCase: register, login
rules:
lengthRule:
express: <=255 chars
characterRule:
express: value>=97 and value<=122
express: value>=48 and value<=57
```
为了让这个YAML文件能对代码友好我把express表达式部分做了技术处理ASCII值从97到122之间是az的字符48到57是数字09。
然后我们写一段代码从这个YAML文件中直接把规则加载进来在内存中形成一个分类树。
![](https://static001.geekbang.org/resource/image/dd/9a/dd045b1924fd30754b50c7c51f90d69a.jpg?wh=6675x2935)
1个用户名有2个约束每种约束都取1次不同的等价类那测试案例的组合总共有2 \* 5= 10个测试案例。
如果对每一个等价类再加上权值我们还可以根据权值过滤掉部分权值偏小的测试案例。基于YAML可以生成以下测试案例从案例名字你可以看出用户名的取值规则
TestUserNameLengthLessThan255
TestUserNameLengthBigThan255
TestUserNameAsciiValueLessThan48
TestUserNameAsciiValueBetween48And57
TestUserNameAsciiValueBetween57And97
TestUserNameAsciiValueBetween97And122
TestUserNameAsciiValueBigThan122
这里看到成果是很明显的。因为测试案例的生成是自动化的所以以后需求变化时比如允许用户名出现中文那就在user-rule.yaml里增加一条rule测试案例也会自动被修改测试案例维护工作量等于0。
到这里,我再画个图总结一下这个方案的实现思路。
![图片](https://static001.geekbang.org/resource/image/19/3d/19146e8321dc45e65cedd9052661cf3d.jpg?wh=1920x889)
## 业务的逻辑规则
用等价类的规则表达小试牛刀后,我们尝到了甜头。看来,只要能把规则表达出来,生成测试案例这个工作就可以交给代码去做。我们再找一个更加实用的场景,来看看怎么落地。
在做API测试的时候restAPI的接口一般是通过Open API规范来描述。在设计阶段开发先定义要实现的API接口Client要发送什么样的RequestServer要返回什么样的Response。
比如下面的user-restapi.yaml文件就是遵循Open API规范定义了一个根据name查询User的RestAPI。
```yaml
/api/users:
get:
description: 通过name查询用户.
parameters:
- username
type: string
description: 用户name
responses:
'200':
description: 成功返回符合查询条件的用户列表.
schema:
type: array
items:
$ref: '#/definitions/User'
```
这个接口很简单但它也声明了一个简单的契约Client要想查询User它需要向server发送一个http get请求发送的url格式如下
http://{host}:{port}/api/users?username=liusheng
而server如果查询到了User它应该返回这样一个http status code为200的response内容如下
```yaml
{
"items": [
{
"ID": "123456",
"name": "liusheng",
"age": 18
}
]
}
```
YAML文件里定义接口所用到的关键字像get、description、parameters等等它们都是Open API里定义好的含义也是明确的那么YAML表达出来的规则内容也是可以解析出来的。因此我们同样可以根据规则内容直接生成测试代码。
实际上业界已经有了现成的工具有Spring Clond Contract也有[OpenAPI generator](https://github.com/OpenAPITools/openapi-generator)。
我们这就借用这个工具跑一下,把它下载到本地,运行如下命令行:
```yaml
java -jar openapi-generator-cli.jar generate
>   -i user-restapi.yaml \
>   -g java \
>   --library rest-assured
```
运行后就会生成一个基于RestAssure的测试项目。这个自动生成的项目里包含了API测试运行所需要的Client、Config、Model、API所有代码。
![图片](https://static001.geekbang.org/resource/image/f0/d6/f0bd34e107d3bd7594fbea0bcda010d6.png?wh=582x522)
对照上图UserApi.java里的testGetUserByName函数就是根据YAML文件的API定义自动生成的测试代码可以直接运行。
```java
@ApiOperation(value = "Get user by user name",
notes = "通过name查询用户")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "成功返回符合查询条件的用户列表") })
public GetUserByNameOper testGetUserByName() {
return new GetUserByNameOper(createReqSpec());
}
```
是不是很酷?一份契约就这样变成了可执行的测试代码,完全不需要任何开发工作量。
在解放生产力这件事上,优秀的工程师从不满足。上面只生成了一个测试案例,能不能生成多个测试案例,做更多的测试覆盖,让这个好点子物尽其用呢?
当然可以按照等价类规则的思路多个测试案例来自于多个约束那我们可以在YAML文件中加入username更多的约束描述。在user-restAPI.yaml文件里username加两行属性minLength是5maxLength是255代表用户名最小长度是5个字符最大长度是255个字符。
```yaml
/api/users:
get:
description: 通过name查询用户
parameters:
- username
type: string
minLength: 5
maxLength: 255
description: 用户name
.............
```
现在我们就可以用等价类规则转变成测试案例的思路解析YAML文件把testGetUserByName分裂生成多个测试方法了
testGetUserByNameLengthLessThan5
testGetUserByNameLengthBetween5And255
testGetUserByNameLengthBigThan255
OpenAPI generator这个开源工具就说到这你可以根据具体需求灵活修改它加入对YAML文件任何属性的解析赋予它测试上的意义使之成为强大的Auto Gen Auto工具为你所用。
到这里我们回顾一下想要做好API测试0代码自动生成的测试案例够多有2个隐含的前提条件要满足
1.API设计先行。在API**设计阶段**就要理清接口规则并把它表达出来。
2.在API接口的规则文件里规则描述得越详细可自动生成的测试案例就会越多。
在实践中我看到很多公司忽视了API设计先行原则开发团队写完代码再生成YAML接口文件然后交给测试人员开发API测试代码这把流程恰好搞颠倒了。本来应该是根据接口设计文档来开发代码开发和测试都能依据设计并行展开工作这个做法也会促进团队在设计阶段考虑好设计。
但是先写代码再产生接口文档实际就会默许甚至鼓励开发人员先信马由缰写代码写出来什么接口都算数。从API测试角度来看测试需求就处在一个不稳定的状态会带**来高昂的自动化测试垂直维护成本**。在第二模块里,你也会看到,设计先行对于开发人员内部协作也至关重要。
另外有了一份定义完备详细的接口设计文档Auto Gen Auto解决方案才可能实现。它不仅能够生成API test还可以生成performance test等等。
有兴趣你可以自己研究一下这块的工具(相关工具有兴趣你可以点[这里](https://github.com/sheng-geek-zhuanlan/awesome-test-automation)了解OpenAPI是其中的一个我们在后面的课程还会提到Spring Cloud Contract也是类似的解决方案。
## 小结
今天我们一起学习了如何通过Auto Gen Auto技术进一步降低我们的开发、维护成本。白盒测试中实现自动生成并不稀奇所以我们把重心放在了更有挑战的黑盒测试领域。
想要自动生成测试案例,需要我们先洞察业务场景里的规则,并把它表达出来,形成文档,在团队里达成共识,作为**开发和测试的契约**。
我们先从黑盒测试里最熟悉的等价类案例设计方法开始把用户名的命名规则整理清楚并且把它表达在一个YAML文件里将这个规则作为我们Auto Gen Auto的输入。然后经过加载解析最后输出测试案例。在这个过程中我们关注的是实现思路没有给出具体的实现方案有兴趣你可以自己尝试一下。
在第二个例子里我们着重看业界经验以OpenAPI为例它对RestAPI接口设计不仅有设计规范还提供了一套工具集我们使用了它的Generator生产了一个查询user的测试案例代码你能直观感受到它的便利。而且像OpenAPI generator代码都是开源的你完全可以改造它做适合自己项目的定制化扩展。
做Auto Gen Auto这样的项目带来的ROI是非常高的一次性开发工作量不需要代码维护工作量。重要的话再说一遍**任何提高ROI的努力都是值得去尝试的。**
## 牛刀小试
访问[https://github.com/OpenAPITools/openapi-generator](https://github.com/OpenAPITools/openapi-generator)按照指令运行generator命令在PythonJavaScriptJavaGo四种语言里选一种生成代码。
欢迎你在留言区和我交流互动,也推荐你把这讲内容推荐给身边的测试同学,一起精进技术,提升效率。