09丨关联和断言:一动一静,核心都是在取数据

对每一个性能测试工具来说,关联和断言都是应该具备的基本功能。

但是有很多新手对关联的逻辑并不是十分理解,甚至有人觉得关联和参数化是一样的,因为它们用的都是动态的数据,并且关联过来的数据也可以用到参数化中,但不一样的点是,关联的数据后续脚本中会用到,参数化则不会。断言倒是比较容易理解,就是做判断。

那么到底该怎样理解关联和断言呢?下面我们通过两个例子来思考一下。

关联

现在做性能测试的,有很多都是单纯的接口级测试,这样一来,关联就用得更少了。因为接口级的测试是一发一收就结束了,不需要将数据保存下来再发送出去。

那么什么样的数据需要关联呢?满足如下条件的数据都是需要关联的:

  1. 数据是由服务器端生成的;
  2. 数据在每一次请求时都是动态变化的;
  3. 数据在后续的请求中需要再发送出去。

示意图如下:

其实我们可以把关联的功能理解为取服务端返回的某个值。在这样的前提之下,我们可以把它用在很多场景之下。

举个例子,我们常见的Session ID就是一个典型的需要关联的数据。它需要在交互过程中标识一个客户端身份,这个身份要在后续的交互中一直存在,否则服务端就不认识这个客户端了。

再比如,我们现在用微服务已经非常多了,在Spring Boot中有一个spring-boot-starter-security,默认会提供一个基于HTTP Basic认证的安全防护策略。它在登录时会产生一个CSRF(Cross-Site Request Forgery)值,这个值典型地处于动态变化中。

下面我们来看一下这个值如何处理。

首先,录制登录、退出的脚本。操作如下:

录出的脚本如下所示:

这时直接回放会得到如下结果:

这回你会看到提示了,Unauthorized,没权限。

在回放的脚本中,我们看到了如下的登录返回信息。


同时,在脚本中,我们可以看到登录时会使用到这个值。

下面我们就把它关联了。

首先添加Cookies Manage。JMeter在处理CSRF时,需要添加一个Cookies manager。如下:

这里的Cookie Policy一定要选择compatibility,以兼容不同的cookie策略。

然后取动态值,在返回CSRF值的地方加一个正则表达式提取器来做关联。当然还有更多的提取器,我将在后面提及。

这里的<input name="_csrf" type="hidden" value="(.+?)" />,就是要取出这个动态的变化值,保存到变量csrfNumber中去。

然后,发送动态值出去,将发送时的CSRF值替换成变量。

最后,再回放,就会得到如下结果。

这样我们就能看到可以正常访问了。

这就是一个典型的关联过程。

上面是用的正则提取器,在JMeter中,还有其他的提取器,如下图所示:

使用什么样的提取器取决于业务的需要,比如说如果你返回的是JSON格式,就可以使用上图中的JSON Extractor。

我们在很多的业务中,都可以看到大量的动态数据。所以做关联一定要有耐心,不然就会找得很混乱。

断言

在第8篇文章中,我们讲到手工编写脚本,有一个添加断言的动作。断言就是判断服务端的返回是不是正确的。

它的判断逻辑是这样的:

在压力工具中,我们已经知道要比对的值是什么了,接下来就看服务端返回的对不对了。下面我们来详细说一下这个逻辑。

先写一个POST接口脚本。

执行下,看到如下结果:

添加断言。

关键点来了,我们知道图片中的这个“true”服务端返回的,可是它到底是从服务端的什么地方产生的呢?

下面我们来看一下服务端的代码。处理我们的add请求的,是这样的代码段:

 @PostMapping("/add")
  public ResultVO<Boolean> add(@RequestBody User user) {
    Boolean result = paService.add(user);
    return ResultVO.<Boolean>builder().success(result).build();
  }

我们post出去的数据是:

{
    "userNumber": "00009496",
    "userName": "Zee_2",
    "orgId": null,
    "email": "Zee_2@7dtest.com",
    "mobile": "18600000000"
}

代码中对应的是:

@Override
    public String toString() {
        return "User{" +
            "id='" + id + '\'' +
            ", userNumber='" + userNumber + '\'' +
            ", userName='" + userName + '\'' +
            ", orgId='" + orgId + '\'' +
            ", email='" + email + '\'' +
            ", mobile='" + mobile + '\'' +
            ", createTime=" + createTime +
            '}';
    }

ID是自增的:

  @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "select uuid()")
    private String id;

然后由paServer.add添加到数据库里去:

Boolean result = paService.add(user);

add的实现是:

  public Boolean add(User user) {
    return mapper.insertSelective(user) > 0;
  }

这就是一个关键了。这里return的是mapper.insertSelective(user) > 0的结果,也就是一个true,也就是说,这时在数据库中插入了一条数据:

然后,build返回信息:

public ResultVO<T> build() {
      return new ResultVO<>(this.code, this.msg, this.data);
    }

这个时候,我们才看到下面的提示信息:

{"data":true,"code":200,"msg":"成功"}

也就是说,在数据库中成功插入1条数据之后,把1>0的判断结果,也就是true返回给result这个变量,然后通过public ResultVO<Boolean> add(@RequestBody User user)中的ResultVO返回给压力工具。

用图片来说的话,逻辑就是下面这样的:

通过这一系列的解释,我就是想说明一个问题:断言中的true是从哪来的。

知道了这个问题的答案之后,我们就能理解,为什么这个true很重要了。因为有了它,就说明我们在数据库成功插入了数据。

断言是根据需要来设计的,而设计断言的前提就是完全理解这个逻辑。

当然我们也可以直接这样来写Controller:

 public String add(@RequestBody User user) {
    Boolean result = paService.add(user);
    return "Added successfully!";
  }

这时就没有true了。脚本运行结果如下:

这时断言看似是失败的,因为我们断言判断的是“true”,但服务端没有返回“true”这个字符。而实际上,当我们从数据库中查看时,插入是成功的。

但是这种写法是有问题的,不管数据有没有插入成功,只要在add方法执行了,就会提示“Added successfully!”。

在实际的工作中,也有开发这样写代码,这样的话,断言似乎都是对的,事务也是成功的,但实际上数据库中可能没有插进去数据。

总结

实际上,关联和断言的前半部分是一样的,都是从服务器返回信息中取出数据。但不同的是,关联取来的数据每次都会不同;而断言取出来的数据基本上都是一样的,除非出了错。

对服务端生成的,并且每次生成都不一样的动态变化的数据,那么将其取回来之后,在后续的请求中使用,这种逻辑就是关联。

对服务端返回的,可标识业务成功与否的数据,将其取回来之后,做判断。这种逻辑就是断言。

思考题

最后给你留道思考题吧,你能说一下关联和断言的逻辑是什么吗?它们取数据的特点又是什么呢?

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

精选留言

  • 律飛

    2020-01-03 22:35:43

    关联,有关有联,该数据一定是根据前面的业务获取的,是一个变化动态的,从服务器获得的,否则就可以在脚本中直接写好,变成一个参数了;同时该数据也一定是后面业务得以进行的必须输入,否则就没有存在的意义了;因此,关联数据起了一个承上启下的作用。取数据特点,从服务器返回信息中取数据,这个数据是动态的,且是后续业务必须的输入数据,需要继续使用的。
    断言,美其名曰一言断分晓,明查是对是错矣。提取服务器返回的可判断业务成功的数据,对其进行判断,从而获知业务是否成功。取数据特点,也是从服务器返回信息中取数据,在业务成功时该数据是一样的,主要用于判断,判断结束后一般不会继续使用。

    作者回复

    写的非常好。

    2020-01-04 19:49:51

  • zuozewei

    2020-01-05 01:08:34


    思考题:联和断言的逻辑是什么吗?它们取数据的特点又是什么呢?

    关联:取出前序调用返回结果中的某些动态值,传递给后续的调用。最常见的是唯一标识客户端的「Session ID」。

    断言:又称检查点,断言是我们的预期,主要是保证脚本按照原本设计的路径执行。取的数据是服务端返回的,可标识业务成功与否的数据,并做判断。

    请记住,测试一定要有断言。没有断言的测试,是没有意义的,就像你说自己是世界冠军,总得比个赛吧!
    作者回复

    合理。

    2020-01-05 18:23:52

  • 奔跑的栗子

    2020-01-03 22:44:48

    关联和断言,都是获取特定数据;关联将获取到的数据更新到下一次使用中;断言预知被解除数据的数值,判断执行结果是否正常;
    作者回复

    理解非常正确。

    2020-01-04 19:48:51

  • 小昭

    2020-03-19 19:48:08

    今日思考题:
    你能说一下关联和断言的逻辑是什么吗?
    关联的逻辑:数据由服务端生成且每次生成的数据是动态变化的,通过关联取到的数据在后续的请求中需要再次使用。
    断言的逻辑:对服务端返回的,可标识业务成功与否的数据,将其取回来之后做判断。

    补充:断言是根据需要来设计的,而设计断言的前提就是完全理解后端代码逻辑。(代码很重要,昨天老师也指示了,准备着手学起来)

    它们取数据的特点又是什么呢?
    都是从服务器返回的信息中取出数据,不同的是:关联取的数据,每次都不一样;而正常情况下断言每次取的数据基本上是一样的。

    老师讲的循序渐进,简单易懂。
    作者回复

    理解得很准确。加油!

    2020-03-20 05:50:32

  • 人心向善

    2020-08-05 08:32:20

    断言,也就是loadrunner里的检查点:对脚本执行结果进行成功失败判断

    参数化,a可以模拟,b也可以,同样c也可以去模拟,只不过模拟对象不同,但最终结果是一样的(最大程度的模拟现实环境)

    关联,客户端去向服务器请求数据,服务器返回数据的同时带有一个可变的值或不变的值,当通过脚本形式去访问时,如果你的脚本中在模拟客户端请求时没有带这个值去请求的话服务器端是不认的,因为你的请求没有服务器要的值(没有通过服务器的验证),自然数据也不会给你了,有点类似http里的三次握手🤝
    作者回复

    正解。

    2020-11-02 10:27:14

  • 2020-05-24 22:16:12

    关联——动态的数据获取用于继续的使用
    断言——判断业务逻辑是否OK
    作者回复

    理解的非常对。

    2020-05-29 08:36:51

  • 燃客

    2020-03-27 13:00:56

    断言其实从测试人员的角度来解释,最简单直接的方式就是预期结果与实际结果做比较,判断是否一致,只是在这里,我们将其从人工方式转为了脚本去实现。
    关联,最终体现在了业务上下游中的提取、传递、引用,个人认为可以解释为参数传递与引用,可以是接口与接口之间的、接口与前置条件之间的、后置处理器与断言之间的。
    接口与接口之间:比如A接口的某个返回参数作为了后续接口的入参;
    接口与前置条件之间:比如注册接口要保证注册号码的唯一性,我们在前置条件中做处理后,在入参中使用变量,或者直接使用相关的函数生成
    后置处理器与断言之间:比如一个写入数据库动作的接口,我们在断言时候,可能需要在接口后置处理器中引入JDBC请求进行查询,然后再做如beanshell断言是否符合预期
    作者回复

    理解全部正确。

    2020-03-29 16:08:14

  • 蔡森冉

    2020-03-19 09:20:16

    断言 判断对错,关联只管取用,不管对错。关联取数一定是返回值中某一个特定的变化的值,断言则是判断返回值中有没有某一个特定值,有就说明程序按预定设计执行了
    作者回复

    理解正确!

    2020-03-19 13:29:06

  • 啊啊

    2020-03-17 08:18:38

    接口性能测试,若b接口需要关联a接口的返回数据,如token 。那么,a接口性能会影响到b接口的性能。
    我一般是通过修改代码屏蔽掉这个参数的影响。但我不知道是否合适?
    合适或者不合适,或者如果需要分情况考虑,可不可以请老师帮我理理。感谢
    作者回复

    如果只是为了定位时间消耗在哪里,为了找问题可以这样。真实的场景中肯定不能这样做。

    2020-03-17 08:53:59

  • songyy

    2020-01-14 08:47:05

    关联和断言的逻辑是什么吗?
    它们取数据的特点又是什么呢?

    关联: 在发出的请求之中,用到收到的请求的数据。通常取到的是header或者页面内容数据,用正则表达式比较合适。

    断言: 判断请求是否成功的标志。可以用json parsing的方式获取数据。判断HTTP status code也是一种合理的断言方式(比如,一个post请求,在成功时断言201 created)
    作者回复

    理解的很正确 。

    2020-01-14 11:54:33

  • 餘生

    2020-01-13 19:55:37

    关联,个人认为比较直观的解释:比如操作一个事件,需要前后分别请求A接口和B接口,B接口请求需要A接口返回参数的某些字段,这就是关联。

    断言,没什么好说的,就是判断实际结果是否符合预期结果,并且测试中一定要加,因为有时候无论结果是否正确,response code都是200,不加断言无法直观判断
    作者回复

    理解的很对。

    2020-01-14 12:15:45

  • rainbowzhouj

    2020-01-06 23:18:18

    关联:假设一个业务场景由多个请求构成,那么关联可以理解为前一个请求的输出作为后一个请求的输入。并且可以将关联的值参数化,例如Token,jobId等;
    断言:一个请求从执行开始到结束之中,所经历每个步骤都可以“暂停”,那么暂停的这个动作可以理解为断言。通过断言你可以知道代码的运行逻辑,对应的输出是否合理,Debug的好帮手。
    作者回复

    理解的很对。

    2020-01-07 09:22:57

  • 村夫

    2020-01-03 06:56:38

    老师,工具的使用还有几篇?
    作者回复

    还有参数化的逻辑和http协议的两篇算是和压力工具有关的。再往后就不讲压力工具了。
    就开始讲场景,监控,分析什么的了。
    是不是工具部分太简单了?😀😀

    2020-01-03 07:57:04

  • yab

    2023-02-10 14:04:59

    老师您好,文中说明了关联 我想请教下在压力测试过程中针对接口依赖的问题,在多线程情况下会产生线程竞争就会通知接口依赖传参有时取不到,如果增加锁则又会导致测试数据受到印象,那么最优的处理方式是什么样呢?提前把动态数据做存储然后再调用吗啊
    作者回复

    如果确实会有取不到的情况,一般处理方法也是把动态数据造出来存储好再调用。

    2023-02-24 18:44:04

  • Karen

    2022-04-18 11:56:23

    关联,一定是根据前面的业务获取的,是一个动态的变化值,从服务器返回中获得的;否则就可以直接写死在脚本里,变成一个参数;该数据一定是后面业务进行必要的参数值,关联数据起了一个承上启下的作用。关联特点:从服务器返回信息中取数据,这个数据是动态的,且是后续业务必须的输入数据,需要继续使用的。
    断言,断言就是区分业务对错的。特点:提取服务器返回的值,来判断业务成功的数据,从而获知业务是否成功
    作者回复

    理解的很正确。

    2022-04-23 18:24:05

  • 章鱼

    2022-03-23 14:14:25

    关联--将接口动态返回值,通过参数的方式传递给其他接口,并完成整个业务流程
    断言--通过具体的值 与 接口返回值 进行比较,查看接口是否正确返回值,这个【具体的值】要通过后端的代码进行分析,查看内容是什么
    作者回复

    说的对。

    2022-03-30 08:38:01

  • 周丫人

    2021-11-25 11:57:48

    关联的作用主要是为了构造场景,因为有些接口的请求参数,就是会依赖于其他的接口,比如说需要登录之后才能使用的场景,那么登录接口必然要先被 调用,然后从返回值中提取到对应的数据,用于做后面接口的请求参数。断言的功能,主要是为了判断 这个接口,在业务上执行的是否正确,因为接口能调通和业务上的正确还是有区别的。
    从取数的逻辑上来说,二者都是从返回值中获取所需要的数据。但是作用有所不同。所以 在Jmeter中,我们需要用到不同的元件,来获得数据。
    断言用的就是断言的元件。关联可能先要用正则表达式提取器,然后把获得的数据,用header组件设置进去
    作者回复

    理解非常正确。

    2021-11-27 00:48:22

  • 0909

    2021-06-10 14:51:47

    关联是接口前后依赖,断言是保证接口返回的结果是正确的,符合业务场景的
    作者回复

    非常正确。

    2021-06-10 23:13:25

  • bolo

    2021-02-23 17:50:06

    关联和断言的逻辑?
    相同点:都是从接口的返回体里取数据。
    不同点:
    1、关联是将取到的数据用于后续的接口使用,比如登录后的cookie内容,需要放到后续的接口中使用。 断言是针对单接口的业务逻辑验证。
    2、取的方式不同:关联取的方式主要用于正则,JSON提取器等等; 断言取的方式有包含、匹配,相等、等等方式。


    作者回复

    对的。

    2021-03-22 22:23:29

  • Tomie

    2020-12-19 00:26:02

    老师你好,性能小白强上手,听你你的课程眼前一亮。关联:服务器响应得数据动态生成,不会重复,还会用于后续请求,断言就是针对服务器响应结果有一种判断,响应结果不变。老师我用locust测试性能得时候 单机压测得时候没问题,单机分布式压测多了 就会接口报错,我的初衷是 在线3000,业务并发率5%,tps=3000*5%=150,线程数就是150tps/(1000ms/100ms)=15 ,locust因为没有线程是携程,所以我觉得应该对应得就是孵化率,电脑是6核,就起了6个worker, 用户总数是3000进行压,每个worker到了50多得时候接口就开始就报错了,压倒2500用户就上不去了,我有点怀疑我脚本写的有点问题,能否帮忙看看,或者贴个locust压测范本看看

    def login(s, data):
    url1 = url_data['test']['url1']
    s.get(url1, verify=False)
    url2 = url_data['test']['url2']
    res2 = s.post(url2, data, verify=False)
    url3 = res2.json()['next']
    s.get(url3, verify=False)
    url4 = url_data['test']['url3']
    res4 = s.get(url4, verify=False)
    url5 = res4.json()['redirect_uri']
    s.get(url5)
    res = s.get(url4, verify=False)
    companyId = res.json()['companyId']
    s.headers['X-CSRF-TOKEN'] = s.cookies['X-CSRF-TOKEN']
    assert res.status_code == 200
    return companyId


    class consoleTask(TaskSet):
    def on_start(self):
    data = {"email": "1606724519@linshiyouxiang.net", "password": "xxxxx@"}
    self.companyId = login(self.client, data)
    @task(1)
    def userInfo(self):
    res = self.client.get("/api/v2/userInfo", name="用户信息")
    assert res.status_code == 200
    class User(HttpUser):
    host = "http://xxx.xxx.com"
    min_wait = 3000
    max_wait = 6000
    tasks = [consoleTask]
    作者回复

    这个问题看起来有点意思。
    有没有错误信息和资源信息之类的可以发我微信上,我给你分析一下。

    2020-12-20 12:47:56