<audio id="audio" title="07丨性能测试工具：如何录制脚本？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/61/40/6155c7c2b616c24e0f97b9f6a6afbb40.mp3"></audio>

对于一个性能测试工具来说，如果能实现以下几大功能，那么就基本上就满足了性能测试工具的功能。

1. 录制或编写脚本功能
1. 参数化功能
1. 关联功能
1. 场景功能
1. 报告生成功能

但是除此以外，在工作的细节上还有更多要求，就要看工具的实施能力了。

有很多性能测试工程师希望工具能做得非常全面，又人性化，而纵观当前的性能工具，真正能够做到傻瓜式录制完脚本，自动设置好参数化、关联、场景，直接产出结果的工具是没有的。不管是云性能测试平台，还是分布式性能测试工具（当然性能测试工具几乎全部具有分布式能力），都需要性能测试人员来定义参数化数据、设置关联、配置场景。

因此，在性能测试的过程中，对工具的配置就成为了性能测试工程师的基本能力。

今天，我们就来看下在性能测试工具中，如何录制脚本。今天的文章有些特殊，可能是专栏中少有的，有详细操作的文章。

## 性能工具的脚本能力

性能测试工具的脚本编写能力分为两类，一个是录制，另一个是手工编写。

现在市场上的性能测试工具虽然支持录制功能，但大部分也只是支持HTTP协议。在我们熟知的工具中，也只有LoadRunner支持更多协议的录制能力。不过幸好，现在我们所面对的应用大部分是HTTP协议的应用。

对手工编写脚本的部分，因为大部分都取决于业务场景，所以很难提出共性。如果有人提出针对性的场景，我们再做相应的示例就行。

因此今天的文章将着重讲一下测试工具的录制功能。很多人以为性能工具录制功能非常简单，点几下就能生成一个脚本，但是录制完之后，针对脚本的增强完善就做得非常少了。事实上，针对脚本，我们不仅要录制下来，还要了解录制的原理和录制完之后的脚本增强。不然，在场景中还是会遇到各种各样的问题。

## 性能工具中的录制功能

录制功能从原理上来说，分成两种：

1. 本地录制：通过截取并解析与服务器的交互协议包，生成脚本文件。比如说LoadRunner调起IE的时候，不用修改IE的代理设置，就可以直接抓取HTTP包，并通过自己的解析器解析成脚本。
1. 代理录制：通过代理服务器设置，转发客户端和服务器的交互协议包，生成脚本文件。JMeter中的脚本录制功能就是这样做的。

这两者的不同点主要在于操作上。本地录制相对简单，但有些场景受限，比如说操作只能在某台服务器上，但是这台服务器又不允许安装工具；代理录制操作复杂一些，但可以满足更多的场景。

通过这张图，我们可以简单看到代理录制的逻辑：

<img src="https://static001.geekbang.org/resource/image/2c/49/2c444b52c9b614bd7779394e6ed18849.jpg" alt="">

1. 我们在IP为2.2.2.2上的主机上，打开一个代理程序，开81端口，所有到81端口的都转发到1.1.1.1的80端口。
1. 当3.3.3.3主机要访问1.1.1.1的的80端口，可以通过访问2.2.2.2的81端口进行转发。

这里需要你注意的是，代理是用来转发数据包的，并不是重定向哦。不管是在本机用代理，还是远程用代理，这个逻辑都是不会变的。

有了这个逻辑之后，你要明白的一点是，**客户机不一定要和代理服务器在同一台机器上**。

为什么要强调这一点呢？因为有很多人用工具来录制时，都不知道这个逻辑，只知道工具是那么操作的。这也是很多人不能理解Port mapping的原因。

不同的工具录制方式略有不同。今天我们用常见的两个性能测试工具LoadRunner和JMeter做为示例工具。

### JMeter的录制功能

首先打开JMeter，添加一个线程组，再添加一个HTTP(S) Test Script Recorder。界面如下：

<img src="https://static001.geekbang.org/resource/image/df/f8/df8730ffbbfb0e68879a88e8699035f8.png" alt="">

这里有几个关键点说明一下：

- Target Controller：这里指定录制出的脚本要放到哪里去。如果你想把不同的脚本放到不同的线程组中去，在录制的时候就可以拆分开。
- Grouping：分组，这个分组功能很实用。但是如何分组就和具体的目标相关了，这一点下面我们再细说。

点击start按钮时，会提示创建一个根CA证书。这个证书生成在bin目录中，文件名是：ApacheJMeterTemporaryRootCA.crt，七天有效期。这个证书将被用来客户端转发HTTPS的请求。与此同时，还有另一个证书在同目录中生成，名字是proxyserver.jks，这是JMeter自己生成的根证书。

<img src="https://static001.geekbang.org/resource/image/be/19/be8ef3815ed27b60f11aae9679457219.png" alt="">

前面我们说到了，JMeter是用代理的方式来录制的。如果服务端用了SSL证书，在代理时也要加SSL证书，那么代理录制的结构就会变成这样。

<img src="https://static001.geekbang.org/resource/image/8f/c9/8f51e323947ca6b8109241f1c71e0ec9.jpg" alt="">

上面的SSL证书就是用来处理上图中蓝色的这一部分。

我们点击ok之后，就会出现这个界面。在这个界面中，只有两个配置项。

- Prefix：请求名的前缀。
- Create new transaction after request(ms)：一个请求完成之后，如果下一个请求超出了这里设置的时间间隔，就创建一个新的事务。

<img src="https://static001.geekbang.org/resource/image/bf/ff/bff35c026d490ec75425a0d6e41a36ff.png" alt="">

然后到主机上设置代理。

注意，这里我要敲黑板了呀：这里的代理设置，是在需要访问的客户机上。这个客户机，不一定是压力机所在的机器。这里的localhost，也应该设置的是代理服务所在的主机IP。

<img src="https://static001.geekbang.org/resource/image/74/49/74e3652701b526509b410790f0fa2549.png" alt="">

请注意，如果你要设置为录制HTTPS，还需要做如下两步。

第一步是，浏览器代理要把Secure Web Proxy(HTTPS)选择上，同时填上相应的代理IP和端口，下图是macOS上的图示。

<img src="https://static001.geekbang.org/resource/image/b3/46/b3ef5c0c4ce44566ee31157c17cd1846.png" alt="">

但你会发现，这时仍然录制不了HTTPS应用，访问时会出现如下提示：

<img src="https://static001.geekbang.org/resource/image/35/4f/3530bde553697ac448eac94e26ee524f.png" alt="">

这时就要在客户端机器上导入上面提到的ApacheJMeterTemporaryRootCA.crt。我们打开证书管理软件，在macOS上是Keychain Access，Windows上是certmgr.msc。

这里以macOS为例。

首先打开Keychain Access。

<img src="https://static001.geekbang.org/resource/image/68/2e/6861248706928c135e68bed9ee28232e.png" alt="">

点击上图中的Import Items。选择ApacheJMeterTemporaryRootCA.crt，导入之后选择证书。会看到如下提示：

<img src="https://static001.geekbang.org/resource/image/dd/92/ddfdc4ef2050f922c9ea1b7ed3977d92.png" alt="">

因为这个证书不在系统信任的默认列表里，所以会提示证书不可信。

另外这里我可以再多说一句，你注意的是，全球的可信任的根证书都是默认添加到系统中的，如果你在访问网站时，提示你要安装什么证书，一定要明确知道证书是从哪来的，不要随意安装未知来源的证书。目前国内的HTTPS覆盖度不高，仍然有大量的HTTP网页，这是需要推进的网络安全之一。

然后我们双击此证书。

<img src="https://static001.geekbang.org/resource/image/f7/16/f779bc63cd421ba92b1de098899b5616.png" alt="">

改为Always Trust即可。提示如下：

<img src="https://static001.geekbang.org/resource/image/41/8c/417c1e58d3081ca6515ec8977532b78c.png" alt="">

这时，HTTP和HTTPS都会被录制下来。然后在客户机上打开浏览器，访问你的页面，这样就录制到脚本了。

下面我们再来说下Grouping这个功能。

Grouping的设置有如下几种，如果需要将脚本分开，先确定需要如何拆分。示例如下：

<img src="https://static001.geekbang.org/resource/image/22/8b/2214f144340139acd8c84a15c86c938b.png" alt="">

第一个选项是Do not group samples，也就是不分组。

这是很多人使用的默认选项，这就相当于没有事务的概念了，每个请求都会单独统计TPS和响应时间信息。

<img src="https://static001.geekbang.org/resource/image/41/83/41bb7af036a9910db9d8fd4bb6cf4083.png" alt="">

第二个选项是Add separators between groups，在组间添加分隔，就为了好看！

<img src="https://static001.geekbang.org/resource/image/25/35/251bcebb7e4451c881ba718ca21d6535.png" alt="">

第三个选项是，Put each group in a new controller，每个组放一个新的控制器。这是一个Simple Controller，它的作用也是只有一个：就为了好看！

因为脚本太长了，看起来不方便，所以分个组，看着清晰一些。话说回来，你们见过在JMeter中有很长脚本的吗？是不是很多人都没有见过？

<img src="https://static001.geekbang.org/resource/image/74/0a/7451c99638e005cfe98d80ed0def060a.png" alt="">

第四个选项是，Put each group in a new transaction controller，将每个组放入一个新的事务控制器中。

<img src="https://static001.geekbang.org/resource/image/9b/34/9b5766b5a61fe76ed82a33f671ae9c34.png" alt="">

Transaction Controller和Simple Controller的区别就是Transaction Controller会做为事务统计脚本执行的时间，而Simple controller不会。

第五个选项是Store 1st sampler for each group only，只存储每个组的第一个样本。

网上大部分都只描述了上面这句，但是请注意我这里还有一句关键的：从HTML文件获取所有内含的资源和自动重定向将开启。也就是说，虽说只记录了一个Sampler，但是资源也会下载，重定向也会开启。

我们把这个过程抓出来看一下，因为JMeter没有把这个过程显示出来。所以这里用Chrome Developer Tool抓一下看看。举例来说，我们在浏览器里只输入了一个https://www.jd.com。抓出如下结果。

<img src="https://static001.geekbang.org/resource/image/5a/a4/5a24f2adae925faac98b057a507525a4.png" alt="">

在上面的图中，你可以看到，www.jd.com，第一个就是307 Internal Redirect。接着请求Document，然后下面是静态资源。在录制时，选择Store 1st sampler for each group only之后，只会录制到第一个请求，而后面这些在回放脚本时也都会访问。

在JMeter的代理录制中，还有一个界面如下：

<img src="https://static001.geekbang.org/resource/image/96/0d/968fe93634c0f18c806fa922f300d50d.png" alt="">

中文界面中通常将之翻译为包含模式、排除模式。“模式”一词一加就显得格外高大上了。

通常这里都会写上正则表达式，比如说常用的一些：

```
.*
.*.png
.*.gif
.*.jpg
.*.php
.*.jsp
.*.html
.*.htm
.*.js
..(js|css|PNG|jpg|ico|png|gif).

```

由于正则是一个很大的话题，这里我们就不展开了，只要你懂正则，在这里就可以适用。

通过上面的内容，我们已经把JMeter录制的原理和操作的过程都详细地描述了一遍，关于JMeter的录制功能，就介绍到这里。

在此重点提醒你一下，录制是通过代理做的，一定要知道代理的原理，代理就是转发的功能。

### 承上启下的话

为什么JMeter这样的功能单一，性能又不好的性能测试工具能这么快的占领市场呢？

在我看来，工具能不能用取决于它能不能满足需要。在很多的性能测试场景中，JMeter已经够用了。因为性能压力工具只需要两条曲线：TPS和响应时间（如果出错最多就再看一下错误率曲线）。这些功能，JMeter都可以提供。

现在的性能项目中，我们要的压力其实并没有很大，并且大部分都是HTTP、TCP之类的常见协议，脚本所使用的资源并不多。一般能达到万级TPS的都很少很少，所以弄几个机器，JMeter也就够用了，再加上免费开源，何乐而不为呢？

而LoadRunner的失败之处就是价格高，更新慢。一想到HP糟蹋了LoadRunner，我就伤心落泪。

### LoadRunner中的录制功能

我们都知道LoadRunner其实可以录制很多协议，这也是它前期扩展市场的很重要的功能。应该说，在录制这个功能点上，所有的性能测试工具都不如LoadRunner。并且LoadRunner在其他很多功能上都是强大的，强大到什么程度呢？就是有很多你不需要的，不常用的功能，它都具备。

很多人都知道，LoadRunner中的Vuser Generator只支持Windows。你有没有想过这是为什么？其实解释起来也简单，LoadRunner一开始是基于WinInet做的，就是Windows Internet API。后来可能是觉得WinInet太恶心了，于是换成了Windows socket。而Windows socket跟UNIX socket还是有一些小区别。

所以从历史延续下来，Vuser Gnenerator就一直在Windows上了。

为什么不做UNIX的版本呢？其实在我看来，完全没有这个必要。因为Load Generator已经支持UNIX了。从使用的角度说，Vuser Gnenerator没有必要做UNIX的版本，因为它还有Port Mapping的功能，这样在UNIX上的操作也照样录得下来。。

下面我们就单说LoadRunner的录制功能。

### 常规录制

首先，我们打开Vuser Generator，点击Start Record，出现如下界面：

<img src="https://static001.geekbang.org/resource/image/c7/67/c79f3d7d072478b223d557987951c767.png" alt="">

在这个图中，首先的选择是：

<img src="https://static001.geekbang.org/resource/image/0c/b7/0c0600701f1c6b71c8389eb243ab8bb7.png" alt="">

这里用IE或者应用程序都可以，只要支持我们选择的HTTP协议就行。

Recording into action这里是默认的action，请你一定要注意的是init、action、end这三个都是action，并没有什么区别。控制init和end只执行一遍和action会重复执行多次的功能也不在它们自己身上，而是在run logic里。这一点我将在后面的文章中再细说。

点击Options之后，跳出界面如下：

<img src="https://static001.geekbang.org/resource/image/21/c9/21e1969112927b0e16adc2e013241cc9.png" alt="">

在这个界面中，有很多可以调的内容。这里举几个重要的点。

首先是HTML-based script和URL-based script。

<img src="https://static001.geekbang.org/resource/image/d5/16/d57f3c15ea014cf1502e136a590b4e16.png" alt="">

这个功能点之所以重要，是因为这两个选项录制出来的脚本有很大差别。

其实这一点和JMeter的Store 1st sampler for each group only是一样的含义。

如果选择了HTML-based script，就是一个页面一个请求了，而在回放和压力时，这个页面的所有资源都会请求。

如果选择了URL-based script，就是每个资源一个请求。这个选项有好处是，便于控制和查找问题。如果不想要某个资源，直接注释掉就好。

其次，我们需要注意关联功能。

<img src="https://static001.geekbang.org/resource/image/52/43/52e00fd4eb4e6dab16f0acee5031d843.png" alt="">

你可以在这里事先设置好关联的规则，比如说这样的：

```
JSESSIONID=5687300192384o4^&amp;&amp;^&amp;890523#123456;

```

你就可以设置左边界为：JSESSIONID=，左边界为分号，然后在你录制的时候，如果规则匹配到就会自动创建关联。

点击OK之后就开始录制了。出现一个工具条，如下所示：

<img src="https://static001.geekbang.org/resource/image/2d/93/2dac210c135e85e99d8e0405d72a3093.png" alt="">

在这个功能条上具有的功能是：暂停、停止、新建Action，创建集合点、创建事务的起点和终端、加备注、加文检查点。

一般在业务流比较长的脚本中，<br>
性能测试工程师都会通过新建Action把操作区分开，也会在录制过程中创建好必要的事务。

最后录制出的脚本如下：

<img src="https://static001.geekbang.org/resource/image/9f/3c/9fe88180c56b27edd542db9ff715d73c.png" alt=""><br>
注意哦，URL-based script的时候，有一个concurrent group，这个并发组是同时发出请求的。<br>
在JMeter中有一个Parallel Downloads，你还记得吗？

这两者功能一样。

上面就是LR中常规的录制功能。录制前，看下readme，看LR支持什么浏览器。在版本12.6的readme中，已经声明支持Windows 10 + IE了。但是我们在使用的过程中还是遇到各种各样的问题，比如调不出浏览器、录不出脚本、卡死的问题。

还有，有些应用只支持Chrome，而有时，有些应用只能在某些特定的机器的执行，而那些机器又不能装Vuser Generator。

在这样的场景中，我们只能使用Port Mapping的功能。是的，在LoadRunner中，Port Mapping就是代理录制的方式。

### Port Mapping

首先打开Vuser Generator，点击Start Record，配置成如下界面：

<img src="https://static001.geekbang.org/resource/image/55/37/554a3e2ef78259e73ecc4616bc176c37.png" alt="">

注意，这里一定要选择的是LoadRunner安装目录bin中的wplus_init_wsock.exe，从这个名字你也能知道它是基于Windows Socket的。

然后，点击Options - Port Mapping，如下所示：

<img src="https://static001.geekbang.org/resource/image/84/29/847bf5c6d80b59f3772cb07d12c95929.png" alt=""><br>
点击New Entry。配置如下：

<img src="https://static001.geekbang.org/resource/image/b3/49/b39d5e0317acd982a42f97f5689a0d49.png" alt="">

从上图中你可以看到，它的代理功能是很全面和强大的，不仅支持不同的Service ID，也支持SSL。

这时的访问逻辑是下面这样的：

<img src="https://static001.geekbang.org/resource/image/0a/8e/0ab937e601a8f2029527be1ccb0de08e.jpg" alt="">

一路OK，返回之后我们就可以开始录制了。会打开一个代理程序。截图如下：

<img src="https://static001.geekbang.org/resource/image/b8/36/b8cc2119b6eeb7164bbb47987d88ed36.png" alt="">

这时候本地会开一个92的端口。

<img src="https://static001.geekbang.org/resource/image/59/74/591d5598781d5a107b2d56f93a2e6f74.png" alt="">

请注意，这时如果是远程访问，要注意不要让防火墙拦截了。

接着打开浏览器，输入地址http://10.211.55.3:92/，可以看到打开的是http://39.105.21.22:91/的界面。

<img src="https://static001.geekbang.org/resource/image/dc/ca/dc5d435bca5f519905a7e49db49e72ca.png" alt="">

同时，录制工具条中也显示出有事件产生。

<img src="https://static001.geekbang.org/resource/image/5d/84/5d4e718b7cfd98398b4dc915180b5784.png" alt="">

当我们停止录制后，查看脚本如下：

<img src="https://static001.geekbang.org/resource/image/3a/98/3acc0dac595c23bedc5229f966d15398.png" alt="">

看到没有，这里的访问IP在直接回放时是不对的。所以要将ip:port换成39.105.21.22:91才能回放。替换后如下：

<img src="https://static001.geekbang.org/resource/image/7d/eb/7d699171119d5aab83af784220e421eb.png" alt="">

这样就可以回放成功了。

<img src="https://static001.geekbang.org/resource/image/61/d8/613e6d89e4480263bbce1c2aa08f0dd8.png" alt="">

如果回放不成功，我们就需要根据出错日志判断要做什么样的脚本增强。大部分的脚本都是需要做关联的，所以后面我们将讲一下关联的功能如何做，以及关联的原理。

Loadrunner的Port Mapping还可以支持FTP、SOCKET、POP等协议。这个功能点也不复杂，操作起来也简单，只要想明白访问链路就可以了。

LR的录制常用功能基本就这些了。

## 总结

这篇文章，应该是我写的所有的文章中，最最基础的一篇了，并且，从操作上，一步步地描述，也比较清晰。如果你有性能工具使用经验，肯定会觉得这篇过于简单。

可是为什么还要写呢？

因为在性能测试的过程中，有很多新手对录制的逻辑并不清楚。代理录制的这个动作他们也可以很快学会。但是很快就忘记了，我曾经给一些人手把手教过如何做代理录制。结果第二天就不记得了。其实并不是不记得动作，而是出了问题，脑子里没有判断问题的逻辑，所以根本无从下手排查。

另外，你需要注意的是，录制功能并不是性能测试工具必备的功能。对性能测试工具来说，关键功能是能实现模拟批量的真实请求逻辑。至于脚本是如何实现的，怎么做就是可以的。所以我们可以用其他的工具，比如说BadBoby、Fiddler甚至Wireshark抓到交互请求，再放到JMeter中实现脚本，也完全是可以的。

当然没有脚本就无从实现压力，所以脚本的实现是性能测试工程师必备的基础技术，理解原理也是必须的。

## 思考题

学完今天的文章后，你能用自己的话说一下代理录制的逻辑是什么吗？以及，当访问网页时，为什么第一个请求至关重要？

欢迎你在评论区写下你的思考，也欢迎把这篇文章分享给你的朋友或者同事，一起交流进步一下。
