09丨%08關聯和斷言:一動一靜,核心都是在取資料.
對每一個性能測試工具來說,關聯和斷言都是應該具備的基本功能。
但是有很多新手對關聯的邏輯並不是十分理解,甚至有人覺得關聯和引數化是一樣的,因為它們用的都是動態的資料,並且關聯過來的資料也可以用到引數化中,但不一樣的點是,關聯的資料後續指令碼中會用到,引數化則不會。斷言倒是比較容易理解,就是做判斷。
那麼到底該怎樣理解關聯和斷言呢?下面我們通過兩個例子來思考一下。
關聯
現在做效能測試的,有很多都是單純的介面級測試,這樣一來,關聯就用得更少了。因為介面級的測試是一發一收就結束了,不需要將資料儲存下來再發送出去。
那麼什麼樣的資料需要關聯呢?滿足如下條件的資料都是需要關聯的:
- 資料是由伺服器端生成的;
- 資料在每一次請求時都是動態變化的;
- 資料在後續的請求中需要再發送出去。
示意圖如下:
其實我們可以把關聯的功能理解為取服務端返回的某個值。在這樣的前提之下,我們可以把它用在很多場景之下。
舉個例子,我們常見的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!
”。
在實際的工作中,也有開發這樣寫程式碼,這樣的話,斷言似乎都是對的,事務也是成功的,但實際上資料庫中可能沒有插進去資料。
總結
實際上,關聯和斷言的前半部分是一樣的,都是從伺服器返回資訊中取出資料。但不同的是,關聯取來的資料每次都會不同;而斷言取出來的資料基本上都是一樣的,除非出了錯。
對服務端生成的,並且每次生成都不一樣的動態變化的資料,那麼將其取回來之後,在後續的請求中使用,這種邏輯就是關聯。
對服務端返回的,可標識業務成功與否的資料,將其取回來之後,做判斷。這種邏輯就是斷言。