gitbook/自动化测试高手课/docs/499382.md
2022-09-03 22:05:03 +08:00

247 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 04|脚本复用:什么样的代码才值得写?
你好,我是柳胜。
开发和测试团队我都带过,现在测试人员的代码能力越来越强了,已经接近开发人员,我看过一些测试牛人设计的测试模块和代码:MVC实现测试控制台、分布式多测试节点管理、Proxy对测试Interface默认实现……这些用到的技术栈和开发不相上下。
聊下来,很多的反馈是“能做起来,很庆幸有一个对测试技术很支持的领导”。这就是一个让人困惑的问题,为了开发自动化测试案例,你写了那么多代码,那每一行代码就应该存在它的价值,这个价值是客观的,而不是依赖主观的某个人的认知。不是么?
所以这一讲要关注的问题是,你写的每一行代码都有自动化测试的价值么?你能把它说出来,说清楚么?想清楚这些,你自然也会明白给定一个自动化测试的项目,哪些工作是overwork(过度工作),哪些是underwork(工作得还不够)。
## 哪些代码值得写?
在开始之前,我们再回顾一下自动化测试ROI模型。
![图片](https://static001.geekbang.org/resource/image/cd/00/cd34280bc70b3633e696a7ba16f9e300.jpg?wh=1920x868)
一个案例转化成自动化测试后,我们的目标是它的投资回报率越高越好,在ROI公式里,回报也就是分子越高越好,成本也就是分母越低越好。
在第一讲我讲到过,n是自动化测试案例运行的次数,在回归测试里,n是回归迭代的次数,回归次数越高,n也就越大,这是从时间的角度上来看n。在这一讲,我们换个角度,从空间来看,也就是代码的复用率,我们有没有办法让代码的复用率升高?
### 初始版本
为了让你更容易理解,我们结合一段登录的脚本,一起探索一下怎样提高一个自动化测试案例的复用率。
脚本代码如下:
```java
@Test
public void login() {
WebDriver driver=new ChromeDriver();
driver.manage().window().maximize();
//打开页面
driver.get("https://www.example.com/users/sign_in");
WebElement username=driver.findElement(By.id("user_name"));
WebElement password=driver.findElement(By.id("user_password"));
WebElement login=driver.findElement(By.text("登录"));
//输入用户名
username.sendKeys("liusheng@example.com");
//输入密码
password.sendKeys("123456");
//点击登录按钮
login.click();
}
```
这段脚本实现的功能很简单,启动chrome浏览器,打开一个登录链接,在页面上输入用户名liushing@example.com,密码123456,点击“登录”按钮,完成登录。此处我省去了assert检查点。
现在,这段脚本每运行一次,就测试一次登录。它的n现在就等于1。我们想一下,这个脚本还能怎么提高它的复用率呢?
### 提高复用率:一份代码,多浏览器运行
可以看到,脚本运行的测试案例只在chrome上,但作为一个web应用,一般是要支持市面上主流的浏览器,看一下[阿里云网站](https://help.aliyun.com/document_detail/211434.html)支持12种浏览器,列表如下:
![图片](https://static001.geekbang.org/resource/image/32/5a/32647ecb58ae59f29f1e6aec994f6f5a.jpg?wh=1920x1130)
那么,有没有办法让我们的脚本能够一下子测试12种浏览器呢?此时我们需要修改脚本,支持调用多个浏览器driver:
```java
@Test
@Iteration(Driver=ChromeDriver,FireFoxDriver.....)
public void login() {
//此处是伪代码,代表从Iteration数组里拿到的driver元素
WebDriver driver=new Iteration("driver");
driver.manage().window().maximize();
//打开页面
driver.get("https://www.example.com/users/sign_in");
WebElement username=driver.findElement(By.id("user_name"));
WebElement password=driver.findElement(By.id("user_password"));
WebElement login=driver.findElement(By.text("登录"));
//输入用户名
username.sendKeys("liusheng@example.com");
//输入密码
password.sendKeys("123456");
//点击登录按钮
login.click();
}
```
上面是伪代码,代表Test会重复运行Iteration数组,driver的数目有多少个,就运行多少次,每次运行从iteration数组里取得driver的名字,交给脚本去启动相应的浏览器。
现在dirver有12个,我们就可以让一份脚本测试12个浏览器,获得了n1=12。
总结一下,最佳实践:一份代码,兼容多个浏览器。
### 提高复用率:一份代码,多数据运行
刚才已经迈出了第一步,不错,我们再继续看脚本,还有没有可以改进的地方。现在,我们的脚本只能测试一组用户数据,用户名liusheng,密码是123456。在测试方法论中,一个测试案例应该有多组测试数据,那合法的用户名的数据格式不止这么多,按照字符类型划分等价类,至少有5组:
1.ASCII字符
2.数字
3.特殊字符
4.拉丁文字符
5.中文字符
你如果有兴趣想知道为什么这5种字符是等价类,可以去研究一下字符集原理。
密码一般是数字,ASCII字符加特殊字符3种。我们至少可以开发出5\*3=15种合法的用户名密码组合,作为测试用例。
```java
@Test
@Iteration(UserPassword={xxxx,123456},{测试用户,Welcome1}....)
@Iteration(Driver=ChromeDriver,FireFoxDriver.....)
public void login() {
//此处是伪代码,代表从Iteration数组里拿到的driver元素
WebDriver driver=new Iteration("driver");
driver.manage().window().maximize();
//打开页面
driver.get("https://www.example.com/users/sign_in");
WebElement username=driver.findElement(By.id("user_name"));
WebElement password=driver.findElement(By.id("user_password"));
WebElement login=driver.findElement(By.text("登录"));
//输入用户名
username.sendKeys(UserPassword[0]);
//输入密码
password.sendKeys(UserPassword[1]);
//点击登录按钮
login.click();
}
```
上面的代码是伪代码,UserPassword数组有多少个元素,测试案例就会运行多少次。每次运行会从interation里取得数组的一个元素,一个元素就是一种用户名密码组合。
上面的UserPassword数组有15个元素,我们的测试案例就运行15次,现在n2=15,加上浏览器的12次,n=n1+n2=15+12=27次。
最佳实践:一份代码,多组测试数据。
### 提高复用率:一份代码,多环境运行
现在我们已经摸着道了,提高ROI,那就是让一份自动化测试程序,尽可能多复用在不同的测试场景中。这些测试场景本来就是有效的测试需求,转换成自动化也是一劳多得。
还有没有其他场景呢?当然有,举个例子,在我们的产品发布pipeline里,贯穿了从开发环境、测试环境、准生产环境到生产环境,由低向高的交付过程。
那你的测试脚本需要兼容每一个环境,在所有需要运行它的环境里都可以直接跑,不需要做任何修改。一份脚本,运行在dev,test,stage,productin 4种环境下,我们的n3=4, n=n1+n2+n3=15+12+4=31次
你可能会说,这个有难度呀,环境不同,不光是脚本url不同,里面的测试数据也不一样,测试配置也不一样,甚至timeout要求也不一样等等,不是那么容易实现的。那你可以想想,这种问题,是不是开发也会遇到,他们需要把服务部署运行在不同的环境下。开发是怎么做到的?
以SpringBoot为例,它提供了多profiles配置的功能,application.yml文件配置默认参数,application-dev.yml里放dev环境的配置参数,application-test.yml放test环境的配置参数,application-prod.yml里放production环境的配置参数。
当spring boot application启动时,会自动根据输入环境参数,加载相应的环境yml配置文件。这就实现了一份代码,多环境部署的场景。
我们的自动化测试也可以实现类似的机制。聪明的你,可以考虑自开发一个测试配置文件加载模块,代码不需要多,但会直接增加ROI。
最佳实践:一份代码,兼容多环境运行
### 提高复用率:一份代码,多语言运行
另外,如果你的产品支持多国语言,那么**一份代码跑多国语言版本**,也是一个会显著增加自动化测试ROI的好主意。
像上面的代码,当前只支持中文页面。假设我们的产品要求支持9种语言,那可以让页面控件加载不同语言的label text。
伪代码如下:
```java
@Test
@Iteration(UserPassword={xxxx,123456},{测试用户,Welcome1}....)
@Iteration(Driver=ChromeDriver,FireFoxDriver.....)
@Iteration(Profiles=auto-dev.yml,auto-test.yml,auto-prod.yml....)
@Iteration(Language=en,zh_CN,zh_TW, FR....)
public void login() {
//此处是伪代码,代表从Iteration数组里拿到的driver元素
WebDriver driver=new Iteration("driver");
driver.manage().window().maximize();
//打开页面
driver.get(profile.getUrl());
WebElement username=driver.findElement(By.id("user_name"));
WebElement password=driver.findElement(By.id("user_password"));
WebElement login=driver.findElement(By.text(Label.getLoginText(language)));
//输入用户名
username.sendKeys(UserPassword[0]);
//输入密码
password.sendKeys(UserPassword[1]);
//点击登录按钮
login.click();
}
```
现在一份脚本经过了多浏览器、多数据、多环境和多语言4轮打磨,运行的次数n=n1+n2+n3+n4=12+15+4+9=40次。如果各个场景有关联关系,比如页面的语言和测试数据有耦合,英文页面的encoding和数据的charset有关联,那么两个场景的次数就是完全组合,采用乘法,15\*9=135次。
而且,从脚本的变化可以看到,脚本第一版本里的hard code也一个个被消除了,取而代之的是数据驱动。**消除hard code是提升ROI的结果**。
当然,上面的代码都是伪代码,实际上为了支持多场景运行,付出的努力不止是增加循环那么简单。比如,支持多少种浏览器取决于框架,而不是脚本。像现在有一些新的基于JavaScript的测试框架,只支持chrome浏览器,开发人员很喜欢用它来验证功能,但从自动化测试角度来看,它的ROI就受限了,这些局限都应该框架选型时考虑进去。
## 还有哪些工作值得做?
第一讲提出ROI模型的时候,我就提过,成本里的维护工作量是一个不确定的风险。根据ROI金字塔模型,维护的工作量也是自底向上增加,不确定性增加。
![图片](https://static001.geekbang.org/resource/image/07/f6/07aaf08df0b9a44987ed9a38faa412f6.jpg?wh=1920x1228)
维护工作量的不确定性是自动化测试的一个重要风险,所以我们有必要看一下维护的工作量都花在哪里了。
1.被测截面发生变化带来的维护工作量。比如UI自动化测试的产品页面发生了变化,API自动化测试的接口做了重构。
2.诊断自动化测试的工作量,如果把自动化测试结果分为真阳,假阳,真阴,假阴。那假阳和假阴都是需要诊断的。
诊断是要花费时间的,有这么几块:
1.从错误实际发生,到我们知道错误发生,这有一个**通知**的时间;
2.从开始诊断错误,到定位出错误,这要花费一个**诊断**的时间;
3.修复错误和验证修复方案,这也要时间,即**修复**时间;
4.修复上线后,跑出第一轮测试结果,证明完全恢复,这叫**确认**时间。
![图片](https://static001.geekbang.org/resource/image/e8/f4/e85a5b985324f48fdbeef5de0d9178f4.jpg?wh=1920x459)
怎样能提高诊断的速度呢?从上面的分析,可以看到:
1.缩小通知时间,目标做到**实时**通知。一旦有错误出现,应该立刻有责任人被通知到,进入到诊断环节。
2.诊断越快越好,一旦确定为自动化测试脚本的问题,应该立即对团队做出说明,并将错误案例下线,避免持续出现相同错误,引起误解。
3.修复要彻底,一个案例持续不能给人信任的结果,将会打击团队的信心。我见过有的公司,自动化测试跑完一遍后,还要手工再去验证一遍,这样的自动化测试就失去了价值。
4.确认后,自动化测试快速恢复上线,开始使用。
你在这里,可以发挥技术能力,按照上面的方向努力,去降低自动化测试的维护成本,比如:
1.Log规范+ELK+Grafana实现告警实时传达。
2.检查点+日志+屏幕截图甚至视频,提高诊断效率。
3.高内聚低耦合的模块化设计,能够实现隔离错误,缩小影响范围,快速修复的效果。
4.自动化测试上下线的标准和流程的建立。
![图片](https://static001.geekbang.org/resource/image/3f/26/3ffdb78yy842c96f530158b22e06d026.jpg?wh=1920x591)
## 小结
今天我最想和你传达的观点是:**在提高收益的方向上,我们付出的每一份努力和尝试,都是值得的。**
其实自动化测试有个特点,它并没有一个显式的可验收交付目标。我们既可以写几行脚本就自动化一个案例,也可以实现一个支持控制台、多代理、持久化等功能的复杂系统。
但是别忘了自动化测试的本质,它是一个有业务需求的软件实现。这个业务需求以前没有人去讲清楚,学过这一讲,你就应该明白了,业务需求就是自动化测试ROI,想办法提高它是这个软件的唯一目的。
我讲到了提高代码ROI的通常的四种思路,一份代码,多浏览器运行,多数据运行,多环境运行,多语言运行,并用例子向你展示了,这个提供ROI的过程,也是消除代码Hard code,优化代码结构的过程。
另外,降低维护成本,也是提高ROI的一个非常有效的办法。我们分解了自动化测试的诊断时间,分为通知、定位、修复和确认四块时间,针对每一块时间,都有相应的办法去提高效率,这里我列出了一些常见办法,帮你在工作中找到更精准的目标,你可以保存下来做个参考。
总之,任何能够提高ROI的代码都是有价值的,反之,就是overwork。作为自动化测试架构师的你,在头脑里应该存在一个优先级从高到低的工作列表,先做哪块,再做哪块,按照ROI从高到低的顺序来安排。
你也应该能明白,在一个十几人的团队里,去做一个大规模的自动化测试项目,回报是追不回投入的,这样的项目即使能立项,最后也是死掉。反过来,如果你的领导愿意支持你去做一个复杂的系统,那他要么有一个推广提升的配套计划,要么是一个技术fans,像曾经的我一样,不尊重自动化测试的本质,也会败在ROI的规律手下。
## 思考题
回到这讲开篇的问题,思考一下你目前负责的自动化测试项目,是overwork还是underwork?
欢迎你在留言区和我交流互动,也推荐你把这讲内容推荐给身边的测试同学,一起精进技术。