1. 程式人生 > 實用技巧 >09丨%08關聯和斷言:一動一靜,核心都是在取資料.

09丨%08關聯和斷言:一動一靜,核心都是在取資料.

對每一個性能測試工具來說,關聯和斷言都是應該具備的基本功能。

但是有很多新手對關聯的邏輯並不是十分理解,甚至有人覺得關聯和引數化是一樣的,因為它們用的都是動態的資料,並且關聯過來的資料也可以用到引數化中,但不一樣的點是,關聯的資料後續指令碼中會用到,引數化則不會。斷言倒是比較容易理解,就是做判斷。

那麼到底該怎樣理解關聯和斷言呢?下面我們通過兩個例子來思考一下。

關聯

現在做效能測試的,有很多都是單純的介面級測試,這樣一來,關聯就用得更少了。因為介面級的測試是一發一收就結束了,不需要將資料儲存下來再發送出去。

那麼什麼樣的資料需要關聯呢?滿足如下條件的資料都是需要關聯的:

  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": "[email protected]",
    "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!”。

在實際的工作中,也有開發這樣寫程式碼,這樣的話,斷言似乎都是對的,事務也是成功的,但實際上資料庫中可能沒有插進去資料。

總結

實際上,關聯和斷言的前半部分是一樣的,都是從伺服器返回資訊中取出資料。但不同的是,關聯取來的資料每次都會不同;而斷言取出來的資料基本上都是一樣的,除非出了錯。

對服務端生成的,並且每次生成都不一樣的動態變化的資料,那麼將其取回來之後,在後續的請求中使用,這種邏輯就是關聯。

對服務端返回的,可標識業務成功與否的資料,將其取回來之後,做判斷。這種邏輯就是斷言。