試拆解:httptest+atomic計(jì)數(shù)器的高并發(fā)測(cè)試技巧)
Hey單元測(cè)試拆解httptestatomic計(jì)數(shù)器的高并發(fā)測(cè)試技巧【免費(fèi)下載鏈接】heyHTTP load generator, ApacheBench (ab) replacement項(xiàng)目地址: https://gitcode.com/GitHub_Trending/he/heyHey 是一個(gè)用 Go 語(yǔ)言編寫(xiě)的HTTP 壓測(cè)工具ApacheBench/ab 的現(xiàn)代替代品支持并發(fā)、QPS 限速、HTTP/2 等能力。這篇文章以 Hey 自帶的測(cè)試代碼為教材拆解高并發(fā)單元測(cè)試的兩個(gè)核心技巧用httptest在內(nèi)存中模擬真實(shí)服務(wù)器用atomic原子計(jì)數(shù)器在多 goroutine 并發(fā)下精確統(tǒng)計(jì)請(qǐng)求次數(shù)。全程只讀分析適合想掌握 Go 并發(fā)測(cè)試的讀者。一、項(xiàng)目速覽先認(rèn)識(shí) Hey 的測(cè)試版圖Hey 的代碼非常精簡(jiǎn)測(cè)試代碼只分布在兩個(gè)文件里文件職責(zé)測(cè)試重點(diǎn)hey_test.go命令行入口 hey.go 的參數(shù)解析正則解析請(qǐng)求頭 / 認(rèn)證參數(shù)requester/requester_test.go核心壓測(cè)引擎 requester/requester.go并發(fā)數(shù)、QPS 限速、請(qǐng)求頭、請(qǐng)求體被測(cè)對(duì)象是 Work 結(jié)構(gòu)體N表示總請(qǐng)求數(shù)、C表示并發(fā) worker 數(shù)、QPS表示限速值。它的 Run() 方法會(huì)啟動(dòng)C個(gè) worker 協(xié)程每個(gè) worker 發(fā)送N/C個(gè)請(qǐng)求見(jiàn) runWorkers。 一個(gè)有趣的問(wèn)題壓測(cè)工具本身就是尺子那怎么證明這把尺子量得準(zhǔn)下面四個(gè)測(cè)試用例就是答案。二、技巧一httptest 搭建內(nèi)存服務(wù)器并發(fā)測(cè)試最大的難題是要有一個(gè)真實(shí)收得到請(qǐng)求的 HTTP 服務(wù)端同時(shí)又不能依賴(lài)外部環(huán)境。Hey 的解法是標(biāo)準(zhǔn)庫(kù)net/http/httptestserver : httptest.NewServer(http.HandlerFunc(handler)) defer server.Close()它會(huì)在本機(jī)隨機(jī)端口啟動(dòng)一個(gè)真實(shí)的 HTTP 服務(wù)器帶真實(shí) TCP 連接server.URL就是測(cè)試端點(diǎn)測(cè)試結(jié)束時(shí)defer server.Close()保證清理干凈。這套組合拳帶來(lái)三個(gè)好處零外部依賴(lài)不需要啟動(dòng) Nginx、不需要網(wǎng)絡(luò)權(quán)限CI 環(huán)境里照樣跑請(qǐng)求真實(shí)發(fā)生不像 Mock 那樣只打樁DNS、連接池、keep-alive 都是真的測(cè)出的問(wèn)題更有說(shuō)服力handler 即斷言探針?lè)?wù)端 handler 里記錄到的信息次數(shù)、Header、Body就是測(cè)試的證據(jù)源。三、技巧二atomic 原子計(jì)數(shù)器統(tǒng)計(jì)并發(fā)請(qǐng)求看 TestN它驗(yàn)證20 個(gè)請(qǐng)求 × 2 并發(fā)確實(shí)發(fā)了 20 個(gè)var count int64 handler : func(w http.ResponseWriter, r *http.Request) { atomic.AddInt64(count, int64(1)) } // ... 執(zhí)行壓測(cè)后 if count ! 20 { t.Errorf(Expected to send 20 requests, found %v, count) }這里有兩個(gè)關(guān)鍵決策① 為什么必須用atomic.AddInt64而不是count壓測(cè)場(chǎng)景下多個(gè) worker 并發(fā)打請(qǐng)求服務(wù)器側(cè)多個(gè) goroutine 會(huì)同時(shí)寫(xiě)同一個(gè)count變量。普通自增不是原子操作并發(fā)下會(huì)丟失計(jì)數(shù)data race。sync/atomic包提供的原子自增保證了讀-改-寫(xiě)三步的原子性計(jì)數(shù)永遠(yuǎn)準(zhǔn)確。② 為什么用 int64 而不是 intGo 的atomic整型操作以 64 位為主在 32 位平臺(tái)上對(duì)int64變量要求 64 位對(duì)齊直接聲明int64是最省心的做法。進(jìn)階變體 TestBody它把計(jì)數(shù)條件改成請(qǐng)求體內(nèi)容必須等于Body才 1從而驗(yàn)證壓測(cè)時(shí) POST 的 RequestBody 是否每一次都完整送達(dá)而不只是發(fā)出去過(guò)。四、技巧三WaitGroup time.AfterFunc 斷言 QPS 限速TestQps 要驗(yàn)證設(shè)置QPS: 1時(shí)20 個(gè)請(qǐng)求的發(fā)送速率確實(shí)被限制在約 1 個(gè)/秒。難點(diǎn)在于——限速是時(shí)間維度的行為測(cè)試要等一等才能判斷。它的編排非常精巧go w.Run()把阻塞式的壓測(cè)放到獨(dú)立 goroutine主流程不被卡死time.AfterFunc(time.Second, ...)延遲 1 秒后進(jìn)入回調(diào)斷言count最多為 21 秒限速下允許 ±1 的時(shí)鐘誤差sync.WaitGroup主流程wg.Wait()等斷言執(zhí)行完才結(jié)束測(cè)試避免測(cè)試函數(shù)先返回導(dǎo)致回調(diào)里報(bào)錯(cuò)不生效的經(jīng)典陷阱。對(duì)應(yīng)的被測(cè)邏輯在 runWorker每個(gè) worker 用time.NewTicker制造節(jié)拍發(fā)請(qǐng)求前先-throttle等一拍從而實(shí)現(xiàn)每 worker 每秒 1 請(qǐng)求的節(jié)流。?? 注意斷言寫(xiě)的是count 2而不是count ! 1——并發(fā)/時(shí)間類(lèi)測(cè)試的斷言要給誤差留余量這是 Hey 測(cè)試代碼里最值得抄的一個(gè)細(xì)節(jié)。五、技巧四服務(wù)端回讀斷言請(qǐng)求頭與認(rèn)證信息TestRequest 驗(yàn)證了自定義 Header、Basic 認(rèn)證在壓測(cè)鏈路中不被丟失客戶(hù)端側(cè)req.Header設(shè)置Content-type、X-some并調(diào)用req.SetBasicAuth(username, password)服務(wù)端側(cè)handler 從r中回讀RequestURI、各 Header 與Authorization值賦給外部變量測(cè)試斷言URI 為/、X-some value以及 Authorization 頭精確等于Basic dXNlcm5hbWU6cGFzc3dvcmQ即username:password的 Base64。這種客戶(hù)端寫(xiě) → 服務(wù)端讀 → 主測(cè)試斷言的三段式是把 httptest 價(jià)值榨干的標(biāo)準(zhǔn)姿勢(shì)它驗(yàn)證的是整條 HTTP 鏈路而不是某個(gè)函數(shù)的返回值。六、命令行參數(shù)的正反用例測(cè)試hey_test.go 針對(duì) hey.go 中兩條正則headerRegexp解析-H Key: Value、authRegexp解析-a user:pass做了 5 個(gè)用例正向用例故意使用帶特殊字符的輸入如!Y10K:;(Hepoverflow?)、!!bigmonster1969sid斷言 key/value 拆分正確見(jiàn) TestParseValidHeaderFlag反向用例傳入X|oh|bad-input: badbadbad這類(lèi)畸形輸入斷言必須報(bào)錯(cuò)見(jiàn) TestParseInvalidHeaderFlag邊界用例用戶(hù)名含$*{等元字符時(shí)不應(yīng)解析失敗見(jiàn) TestParseAuthMetaCharacters。正、反、邊界三類(lèi)用例各占一席是命令行參數(shù)解析測(cè)試的完整樣板。七、一鍵運(yùn)行這套單元測(cè)試git clone https://gitcode.com/GitHub_Trending/he/hey cd hey go test ./...強(qiáng)烈建議加上-race參數(shù)go test -race ./...Go 競(jìng)態(tài)檢測(cè)器會(huì)在測(cè)試運(yùn)行期間實(shí)時(shí)發(fā)現(xiàn)未加鎖/未用 atomic 的共享變量讀寫(xiě)——正好與本文atomic 計(jì)數(shù)器主題呼應(yīng)它幫你驗(yàn)證該用 atomic 的地方都用了。八、可復(fù)用的高并發(fā)測(cè)試技巧清單技巧解決的問(wèn)題Hey 中的出處httptest.NewServer內(nèi)存服務(wù)器測(cè)試需要真實(shí) HTTP 端點(diǎn)但不依賴(lài)外部環(huán)境requester_test.goatomic.AddInt64原子計(jì)數(shù)并發(fā)下精確統(tǒng)計(jì)事件次數(shù)requester_test.gogo Run()WaitGroup阻塞式被測(cè)函數(shù)不卡死測(cè)試requester_test.gotime.AfterFunc延時(shí)斷言驗(yàn)證速率/時(shí)間類(lèi)行為requester_test.go斷言留誤差余量 2而非 1消除時(shí)鐘抖動(dòng)導(dǎo)致的偶發(fā)失敗requester_test.gogo test -race靜態(tài)之外的動(dòng)態(tài)競(jìng)態(tài)排查命令行參數(shù)正則解析的正/反/邊界三件套參數(shù)解析覆蓋率hey_test.go總結(jié)Hey 用不到 140 行測(cè)試代碼就為一個(gè)自己會(huì)并發(fā)的壓測(cè)引擎建立了完整可信的自我驗(yàn)證httptest提供真實(shí)的請(qǐng)求落點(diǎn)atomic計(jì)數(shù)器提供無(wú)競(jìng)爭(zhēng)的統(tǒng)計(jì)口徑WaitGroup time.AfterFunc解決了等待時(shí)間流逝這一異步測(cè)試難題。這三個(gè)技巧組合起來(lái)基本可以覆蓋大多數(shù) Go 高并發(fā)場(chǎng)景的單元測(cè)試需求——下次給自己的服務(wù)寫(xiě)壓測(cè)相關(guān)測(cè)試時(shí)直接照著 requester/requester_test.go 抄作業(yè)即可?!久赓M(fèi)下載鏈接】heyHTTP load generator, ApacheBench (ab) replacement項(xiàng)目地址: https://gitcode.com/GitHub_Trending/he/hey創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考