在現(xiàn)代軟件開發(fā)中,有效的測試在確保應(yīng)用程序的可靠性和穩(wěn)定性方面發(fā)揮著關(guān)鍵作用。
本文為編寫集成測試提供了實(shí)用的建議,演示了如何側(cè)重于與外部服務(wù)的交互規(guī)范,使測試更具可讀性和易于維護(hù)。該方法不僅提高了測試的效率,而且還促進(jìn)了對應(yīng)用程序中集成流程的更好理解。通過具體例子,各種策略和工具,例如DSL包裝器,JsonAssert ,并將探索PACT-將為讀者提供一個(gè)全面的指南,以提高整合測試的質(zhì)量和能見度。
本文給出了使用斯波克框架進(jìn)行集成測試的例子,用于測試春季應(yīng)用程序中的http交互。與此同時(shí),它所建議的主要技術(shù)和方法可以有效地應(yīng)用于除了HTTP之外的各種類型的相互作用。
問題說明
文章?訂單混亂:在春季安排TP請求測試 描述編寫測試的方法,明確地將其劃分為不同的階段,每個(gè)階段都執(zhí)行其特定的角色。讓我們根據(jù)這些建議描述一個(gè)測試示例,但是要用一個(gè)而不是兩個(gè)請求來嘲笑。為簡單起見,行為階段(執(zhí)行)將被省略(完整的測試示例可在?項(xiàng)目存放處 ).
該代碼有條件地分為兩個(gè)部分:"支持代碼"(灰色)和"外部交互的規(guī)范"(藍(lán)色)。支持代碼包括測試的機(jī)制和實(shí)用程序,包括攔截請求和模擬響應(yīng)。外部交互的規(guī)范描述了系統(tǒng)在測試期間應(yīng)該與之交互的外部服務(wù)的具體數(shù)據(jù),包括預(yù)期的請求和響應(yīng)。支持代碼為測試奠定了基礎(chǔ),而規(guī)范直接關(guān)系到我們試圖測試的系統(tǒng)的業(yè)務(wù)邏輯和主要功能。
規(guī)范占用了代碼的一小部分,但在理解測試方面具有重要價(jià)值,而占用更大部分的支持代碼則顯示較少的價(jià)值,而且每個(gè)模擬聲明都是重復(fù)的。本守則擬於MockRestServiceServer .提及?威雷莫克的例子 ,你可以看到相同的模式:規(guī)范幾乎相同,支持代碼也各不相同。
本文的目的是為編寫測試提供實(shí)用的建議,這樣的方式將重點(diǎn)放在規(guī)范上,而支持代碼將占次要位置。
演示場景
對于我們的測試場景,我提出了一個(gè)假設(shè)的電報(bào)機(jī)器人,它將請求轉(zhuǎn)發(fā)到開放的API并將響應(yīng)發(fā)送給用戶。
以簡化的方式描述了與服務(wù)交互的合同,以突出業(yè)務(wù)的主要邏輯。下面是展示應(yīng)用程序體系結(jié)構(gòu)的序列圖。我理解,設(shè)計(jì)可能會從系統(tǒng)架構(gòu)的角度提出問題,但請理解這一點(diǎn)--這里的主要目標(biāo)是演示一種提高測試可見性的方法。
提議
本文討論了以下關(guān)于寫作測試的實(shí)用建議:
· 使用DSL包裝器進(jìn)行模擬操作
· 使用JsonAssert 結(jié)果核實(shí)
· 在JSON文件中存儲外部交互的規(guī)范
· 使用契約文件
使用DSL包裝器來嘲笑
使用DSL包裝器可以隱藏模板模擬代碼,并提供一個(gè)簡單的接口與規(guī)范工作。重要的是要強(qiáng)調(diào)的是,所提出的并不是一個(gè)特定的DSL,而是它所實(shí)現(xiàn)的一種一般方法。以下是使用DSL校正測試的例子(?完整測試文本 ).
setup:
def openaiRequestCaptor = restExpectation.openai.completions(withSuccess("{...}"))
def telegramRequestCaptor = restExpectation.telegram.sendMessage(withSuccess("{}"))
when:
...
then:
openaiRequestCaptor.times == 1
telegramRequestCaptor.times == 1
在那里方法 restExpectation.openai.completions 例如,其描述如下:
public interface OpenaiMock {
/**
* This method configures the mock request to the following URL: {@code https://api.openai.com/v1/chat/completions}
*/
RequestCaptor completions(DefaultResponseCreator responseCreator);
}
在代碼編輯器中對方法進(jìn)行注釋時(shí),可以獲得幫助,包括查看將被嘲笑的URL。
在擬議的實(shí)施中,模擬答復(fù)的聲明使用的是 應(yīng)答者 允許定制的實(shí)例,例如:
public static ResponseCreator withResourceAccessException() {
return (request) -> {
throw new ResourceAccessException("Error");
};
}
下面是對不成功情景的一個(gè)例子測試,具體說明了一組響應(yīng):
import static org.springframework.http.HttpStatus.FORBIDDEN
setup:
def openaiRequestCaptor = restExpectation.openai.completions(openaiResponse)
def telegramRequestCaptor = restExpectation.telegram.sendMessage(withSuccess("{}"))
when:
...
then:
openaiRequestCaptor.times == 1
telegramRequestCaptor.times == 0
where:
openaiResponse | _
withResourceAccessException() | _
withStatus(FORBIDDEN) | _
對維雷莫克來說,一切都是一樣的,除了反應(yīng)形成略有不同(?測試代碼 ,?響應(yīng)工廠類別代碼 ).
使用@語言("JSON")注釋以更好地實(shí)現(xiàn)IDD集成
在實(shí)現(xiàn)DSL時(shí),有可能使用 @Language("JSON") 為智能j思想中的特定代碼片段啟用語言特性支持。例如,使用JSON,編輯器將將字符串參數(shù)作為JSON代碼處理,使其具有語法突出顯示、自動完成、錯(cuò)誤檢查、導(dǎo)航和結(jié)構(gòu)搜索等功能。這里有一個(gè)注釋的用法例子:
public static DefaultResponseCreator withSuccess(@Language("JSON") String body) {
return MockRestResponseCreators.withSuccess(body, APPLICATION_JSON);
}
以下是編輯的描述:
使用JSON斷言進(jìn)行結(jié)果驗(yàn)證
…JSONAssert 庫的設(shè)計(jì)是為了簡化JSON結(jié)構(gòu)的測試。它使開發(fā)人員能夠很容易地比較預(yù)期和實(shí)際的JSON字符串具有高度的靈活性,支持各種比較模式。
這樣就可以從這樣的驗(yàn)證描述中移動:
openaiRequestCaptor.body.model == "gpt-3.5-turbo"
openaiRequestCaptor.body.messages.size() == 1
openaiRequestCaptor.body.messages[0].role == "user"
openaiRequestCaptor.body.messages[0].content == "Hello!"
```
to something like this
```java
assertEquals("""{
"model": "gpt-3.5-turbo",
"messages": [{
"role": "user",
"content": "Hello!"
}]
}""", openaiRequestCaptor.bodyString, false)
在我看來,第二種方法的主要優(yōu)點(diǎn)是,它確保了文檔、日志和測試等各種上下文中數(shù)據(jù)表示的一致性。這大大簡化了測試過程,提供了比較的靈活性和錯(cuò)誤診斷的準(zhǔn)確性。因此,我們不僅節(jié)省了編寫和維護(hù)測試的時(shí)間,而且提高了測試的可讀性和信息性。
當(dāng)在彈簧引導(dǎo)中工作時(shí),至少從版本2開始,不需要額外的依賴關(guān)系來使用庫,因?yàn)?org.springframework.boot:spring-boot-starter-test 已經(jīng)包括了 "天空尖叫者": .
在JSON文件中存儲外部交互的規(guī)范
我們可以觀察到,JSON字符串占用了測試的很大一部分。它們應(yīng)該被隱藏嗎?是和不是。了解什么能帶來更多好處是很重要的。隱藏它們會使測試更加緊湊,并使初看之下的測試本質(zhì)更加簡單。另一方面,為了進(jìn)行透徹的分析,有關(guān)外部交互規(guī)范的部分關(guān)鍵信息將被隱藏,需要在文件之間進(jìn)行額外的跳躍。這個(gè)決定取決于是否方便:做對你來說更舒服的事。
如果選擇將JSON字符串存儲在文件中,一個(gè)簡單的選擇是將響應(yīng)和請求分別保存在JSON文件中。以下是測試碼(?全文 )展示實(shí)施方案:
爪哇河
1
setup:
def openaiRequestCaptor = restExpectation.openai.completions(withSuccess(fromFile("json/openai/response.json")))
def telegramRequestCaptor = restExpectation.telegram.sendMessage(withSuccess("{}"))
when:
...
then:
openaiRequestCaptor.times == 1
telegramRequestCaptor.times == 1
… 文件格式 方法簡單地從文件中讀取字符串。src/test/resources 目錄并沒有任何革命性的想法,但仍然可以在項(xiàng)目存儲庫中供參考。
對于字符串的變量部分,建議使用替換?org.apache.commons.text.StringSubstitutor 在描述模擬時(shí)傳遞一組值。例如:
setup:
def openaiRequestCaptor = restExpectation.openai.completions(withSuccess(fromFile("json/openai/response.json",
[content: "Hello! How can I assist you today?"])))
JSON文件中替換的部分是這樣的:
...
"message": {
"role": "assistant",
"content": "${content:-Hello there, how may I assist you today?}"
},
...
在采用文件存儲方法時(shí),開發(fā)人員面臨的唯一挑戰(zhàn)是在測試資源中開發(fā)適當(dāng)?shù)奈募胖梅桨负兔桨?。很容易犯錯(cuò)誤,這會加重處理這些文件的經(jīng)驗(yàn)。解決這一問題的一個(gè)辦法是使用規(guī)格,例如來自PACT的規(guī)格,將在稍后討論。
當(dāng)在用LUovy編寫的測試中使用所描述的方法時(shí),您可能會遇到不便之處:對從代碼中導(dǎo)航到文件的智能j思想沒有支持,但是?預(yù)計(jì)今后將增加對這一功能的支持 .在java中編寫的測試中,這種方法非常有效。
使用契約合同文件
從術(shù)語開始。
合同測試是一種測試集成點(diǎn)的方法,在這里,每個(gè)應(yīng)用程序都進(jìn)行單獨(dú)測試,以確認(rèn)它發(fā)送或接收的消息符合"合同"中記錄的相互理解。"這一方法確保系統(tǒng)不同部分之間的互動符合預(yù)期。
合同測試中的合同是記錄應(yīng)用程序之間交換的消息(請求和響應(yīng))的格式和結(jié)構(gòu)協(xié)議的文檔或規(guī)范。它可以作為驗(yàn)證每個(gè)應(yīng)用程序能夠正確處理集成中其他應(yīng)用程序發(fā)送和接收的數(shù)據(jù)的基礎(chǔ)。
在消費(fèi)者(例如希望檢索某些數(shù)據(jù)的客戶端)和提供者(例如提供客戶端所需數(shù)據(jù)的服務(wù)器上的API)之間建立了合同。
消費(fèi)者驅(qū)動測試是一種合同測試的方法,消費(fèi)者在自動測試運(yùn)行期間生成合同。這些合同將傳遞給提供者,然后提供者運(yùn)行他們的一套自動化測試。將合同文件中的每一項(xiàng)請求發(fā)送給供應(yīng)商,并將收到的響應(yīng)與合同文件中指定的預(yù)期響應(yīng)進(jìn)行比較。如果兩個(gè)響應(yīng)都匹配,這意味著消費(fèi)者和服務(wù)提供者是兼容的。
最后,PACT是實(shí)現(xiàn)消費(fèi)者驅(qū)動的合同測試?yán)砟畹囊环N工具。它支持基于http集成和消息集成的測試,側(cè)重于代碼第一測試開發(fā)。
正如我前面提到的,我們可以使用PACT的合同規(guī)格和工具來完成我們的任務(wù)。實(shí)現(xiàn)可能是這樣的(?完整測試代碼 ):
setup:
def openaiRequestCaptor = restExpectation.openai.completions(fromContract("openai/SuccessfulCompletion-Hello.json"))
def telegramRequestCaptor = restExpectation.telegram.sendMessage(withSuccess("{}"))
when:
...
then:
openaiRequestCaptor.times == 1
telegramRequestCaptor.times == 1
合同檔案是?可供審查 .
使用契約文件的優(yōu)點(diǎn)是,它們不僅包含請求和響應(yīng)體,而且還包含外部交互規(guī)范的其他元素--請求路徑、標(biāo)題和http響應(yīng)狀態(tài),允許基于這種契約充分描述模擬。
重要的是要注意到,在這種情況下,我們只限于合同測試,而不擴(kuò)展到消費(fèi)者驅(qū)動的測試。然而,有人可能希望進(jìn)一步探索《公約》。
結(jié)論
本文回顧了在與彈簧框架開發(fā)的背景下提高集成測試的可見性和效率的實(shí)際建議。我的目標(biāo)是集中在明確定義外部交互的規(guī)范和最小化模板代碼的重要性。為了實(shí)現(xiàn)這一目標(biāo),我建議使用DSL包裝器、JSON斷言、在JSON文件中存儲規(guī)范,并通過PACT與合同一起工作。本文描述的方法旨在簡化編寫和維護(hù)測試的過程,提高測試可讀性,最重要的是,通過準(zhǔn)確反映系統(tǒng)組件之間的交互,提高測試本身的質(zhì)量。