日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

golang 读取文件最后一行_测试用例是开发人员最后一块遮羞布

發(fā)布時(shí)間:2023/12/10 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 golang 读取文件最后一行_测试用例是开发人员最后一块遮羞布 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

測(cè)試用例是開(kāi)發(fā)人員最后一塊遮羞布

最近一周寫(xiě)一個(gè)比較復(fù)雜的業(yè)務(wù)模塊,越寫(xiě)到后面真心越心虛。操作越來(lái)越復(fù)雜了,代碼也逐漸凌亂了起來(lái)。比如一個(gè)接口,傳入的是一個(gè)比較復(fù)雜的大json,我需要解析這個(gè)大json,然后根據(jù)json中字段進(jìn)行增刪改查,調(diào)用第三方服務(wù)等操作。告訴前端接口已經(jīng)完成的時(shí)候,總是有點(diǎn)沒(méi)有底氣。說(shuō)實(shí)話(huà),在寫(xiě)PHP的時(shí)候,我確實(shí)很少寫(xiě)單元測(cè)試,大都是對(duì)著頁(yè)面進(jìn)行一波一波的測(cè)試,現(xiàn)在想想,一個(gè)是懶,還有一個(gè)是確實(shí)PHP是不需要編譯的語(yǔ)言,沒(méi)有編譯時(shí)間,測(cè)試-修正,整個(gè)流程非常短。但是這次是一個(gè)比較大的GoLang項(xiàng)目,如果還是按照“編譯-起服務(wù)-調(diào)用-調(diào)整代碼-編譯-起服務(wù)-調(diào)用-...” 這種循環(huán)來(lái)做調(diào)試,真是會(huì)瘋了的。所以我能靜下心好好研究研究如何寫(xiě)Golang的單元測(cè)試了。

數(shù)據(jù)庫(kù)怎么辦?

這個(gè)是第一個(gè)需要思考的問(wèn)題。這個(gè)問(wèn)題和語(yǔ)言無(wú)關(guān)。一旦有數(shù)據(jù)庫(kù)操作,就需要考慮如何在測(cè)試用例中如何處理數(shù)據(jù)庫(kù)操作。我想了想,無(wú)外乎兩種做法,一種是直接mock數(shù)據(jù)庫(kù)的返回對(duì)象。另外一種,是搭建一個(gè)測(cè)試DB,然后灌入假數(shù)據(jù),進(jìn)行測(cè)試。這兩種方式我選擇了后一種。有幾個(gè)理由:首先,mock數(shù)據(jù)庫(kù)返回?cái)?shù)據(jù)是一個(gè)比灌入DB數(shù)據(jù)更為復(fù)雜的邏輯,數(shù)據(jù)庫(kù)返回的數(shù)據(jù)根據(jù)sql各種各樣,要想在每個(gè)環(huán)節(jié)都寫(xiě)好數(shù)據(jù)庫(kù)操作返回,倒不如我直接偽造一些數(shù)據(jù)來(lái)的方便。其次,mock數(shù)據(jù)庫(kù)返回會(huì)丟失model層的測(cè)試邏輯,當(dāng)然如果你是輕model層,整個(gè)model就只有一個(gè)orm,這個(gè)可能就不是理由了。

所以,我操作的第一步,從線上把數(shù)據(jù)庫(kù)表結(jié)構(gòu)copy一份到我本地vagrant的mysql中。

這里必須要注意,你的測(cè)試數(shù)據(jù)庫(kù)和測(cè)試代碼最好是同一個(gè)機(jī)器上,否則每跑一個(gè)測(cè)試用例,消耗的時(shí)間非常大,你的測(cè)試體驗(yàn)也不會(huì)太好。

第三方請(qǐng)求怎么辦?

我的代碼邏輯中也有一些第三方調(diào)用,調(diào)用其他服務(wù)。當(dāng)然這里也有同樣的兩種辦法,一種是直接在本地測(cè)試環(huán)境搭建第三方服務(wù),另外一種是mock第三方服務(wù)的返回?cái)?shù)據(jù)。這里我選擇了mock數(shù)據(jù)的方式。基本想法是因?yàn)槲疫@個(gè)測(cè)試畢竟不是一種全鏈路測(cè)試,測(cè)試的主體還是我的服務(wù),我的服務(wù)基本上只包含服務(wù)+DB,如果要搭建第三方服務(wù),這就有點(diǎn)舍本逐末的感覺(jué)了。

好了,如何mock第三方服務(wù)呢?

查了下golang中mock的包有兩個(gè)比較出名,一個(gè)是golang官網(wǎng)出品的golang/mock,另外一個(gè)是monkey(https://github.com/bouk/monkey)。兩個(gè)相比之下,我感覺(jué)golang/mock是師出有名,但是不如monkey好用,monkey屬于黑科技,使用修改函數(shù)指針的方式進(jìn)行mock函數(shù)。我想了想,實(shí)用第一位,投入了monkey的懷抱。

基本使用代碼如下:

// mock路網(wǎng)接口

guard := monkey.Patch(lib.Curl, func(trace *lib.TraceContext, CurlType, urlString string, data url.Values, addToken bool) ([]byte, error) {

return []byte("{[\"10010\":\"后廠村路\"}"]), nil

})

defer guard.Unpatch()

將lib.Curl整個(gè)函數(shù)給mock了,并且在函數(shù)結(jié)束后修改mock的函數(shù),保證不影響其他測(cè)試用例。

配置文件怎么辦?

web服務(wù)一般都會(huì)有讀取配置的代碼,我的服務(wù)是讀取一個(gè)參數(shù)config=base.json來(lái)進(jìn)行配置的讀取的。go test中是沒(méi)有辦法給test的代碼傳遞參數(shù)的,(我看網(wǎng)上的一些文章說(shuō)有個(gè)-args的參數(shù),但是我在go1.11版本中確實(shí)沒(méi)有看到這個(gè)參數(shù))。于是我只能選擇使用環(huán)境變量的方式。在運(yùn)行g(shù)o test的時(shí)候,在最開(kāi)頭的部分設(shè)置下當(dāng)前這個(gè)go test的環(huán)境變量CONFIG_PATH,然后修改下我的初始化配置文件的代碼,允許傳入?yún)?shù)進(jìn)行配置文件的讀取。

大概代碼如下:

在運(yùn)行g(shù)o test的時(shí)候設(shè)置環(huán)境變量:

CONFIG_PATH=/home/vagrant/foo/conf/yejianfeng/base.json go test foo/signaledit/... -v -test.run TestGetGroups

測(cè)試環(huán)境的初始化配置文件邏輯:

package test

import (

..

)

var HasSetup = false

// signalEdit初始化,只調(diào)用一次

func SetUpSignalEdit() {

if HasSetup == false {

gin.SetMode(gin.TestMode)

confPath := os.Getenv("CONFIG_PATH") // 獲取環(huán)境變量

commonlib.Init(confPath, "") // 初始化配置文件

conf.ParseLocalConfig()

db.InitDB()

HasSetup = true

}

DestroyTestData(db.EditDB)

CreateTestData(db.EditDB)

}

web怎么進(jìn)行單元測(cè)試?

關(guān)于這個(gè),httptest這個(gè)包提供給我們想要的邏輯了,網(wǎng)上的文章也一大堆了。使用起來(lái)也是很方便,

router := gin.New()

jc := Controller{}

// 燈組模型表獲取信息

router.GET("/group/all", jc.GroupAll)

...

//構(gòu)建返回值

w := httptest.NewRecorder()

//構(gòu)建請(qǐng)求

r, _ := http.NewRequest("GET", "/group/all?logic_junction_id=test_junction", nil)

//調(diào)用請(qǐng)求接口

router.ServeHTTP(w, r)

resp := w.Result()

body, _ := ioutil.ReadAll(resp.Body)

就沒(méi)有什么好說(shuō)的了。

關(guān)于數(shù)據(jù)初始化和銷(xiāo)毀

既然我選擇使用本地DB進(jìn)行測(cè)試,那么按照邏輯,需要在測(cè)試用例開(kāi)始初始化DB數(shù)據(jù),然后在測(cè)試用例結(jié)束后銷(xiāo)毀數(shù)據(jù)。這里我還選擇在測(cè)試用例開(kāi)始的時(shí)候,先銷(xiāo)毀數(shù)據(jù),然后初始化數(shù)據(jù),測(cè)試用例結(jié)束的時(shí)候不要銷(xiāo)毀數(shù)據(jù)。這樣做我承認(rèn)有不好的地方,就是有可能會(huì)有臟數(shù)據(jù)。比較好的地方,就是我在單個(gè)測(cè)試用例跑完的時(shí)候,我有機(jī)會(huì)去數(shù)據(jù)庫(kù)看一眼現(xiàn)在數(shù)據(jù)庫(kù)里面的測(cè)試數(shù)據(jù)是什么樣子。

不管怎么洋,數(shù)據(jù)初始化和銷(xiāo)毀的工作就變得異常重要了,它們必須是冪等,而且可以循環(huán)冪等。(銷(xiāo)毀-初始化)=(銷(xiāo)毀-銷(xiāo)毀-初始化)=(初始化-銷(xiāo)毀-初始化)。要做到這個(gè)我的感受必須借助具體的業(yè)務(wù)數(shù)據(jù)表邏輯了。比如我的所有數(shù)據(jù)表都有一個(gè)路口id的字段,那么我就很容易做到銷(xiāo)毀的冪等,我每次銷(xiāo)毀的時(shí)候,就只要把這個(gè)路口的所有數(shù)據(jù)刪除就可以了。如果沒(méi)有的話(huà),由于我們的數(shù)據(jù)庫(kù)是本地?cái)?shù)據(jù)庫(kù),不妨采用整個(gè)數(shù)據(jù)表清空的方式操作。

數(shù)據(jù)初始化和銷(xiāo)毀的函數(shù)我封裝成兩個(gè)函數(shù),放在一個(gè)包里面

var (

SignalID = int64(999999)

LogicJunctionId = "test_junction"

)

// 創(chuàng)建測(cè)試數(shù)據(jù)

func CreateTestData(db *gorm.DB) {

// SignalInfo表創(chuàng)建一條數(shù)據(jù)

signalInfo := &models.SignalInfo{}

signalInfo.Id = SignalID

signalInfo.Name = "測(cè)試路口id"

signalInfo.LogicJunctionId = LogicJunctionId

signalInfo.Status = 1

db.Create(signalInfo)

}

// 銷(xiāo)毀測(cè)試數(shù)據(jù)

func DestroyTestData(db *gorm.DB) {

db.Delete(&models.SignalInfo{}, "logic_junctionid=" + LogicJunctionId)

...

}

然后把上面說(shuō)的初始化操作封裝成一個(gè)函數(shù)

var HasSetup = false

// signalEdit初始化,只調(diào)用一次

func SetUpSignalEdit() {

if HasSetup == false {

gin.SetMode(gin.TestMode)

confPath := os.Getenv("CONFIG_PATH")

commonlib.Init(confPath, "")

conf.ParseLocalConfig()

db.InitDB()

HasSetup = true

}

DestroyTestData(db.EditDB)

CreateTestData(db.EditDB)

}

所有測(cè)試用例都先調(diào)用下這個(gè)函數(shù)

func TestGetGroups(t *testing.T) {

test.SetUpSignalEdit()

...

}

這里真心要吐槽下testing框架,既然做了測(cè)試框架,SetUp函數(shù),SetDown函數(shù)這些都不考慮,和主流的測(cè)試框架的思想真的有點(diǎn)偏差,導(dǎo)致像這種“普通”的初始化的需求都要自己寫(xiě)方法來(lái)繞過(guò),至少testing框架為應(yīng)用思考的東西還是太少了。

測(cè)試用例的粒度

我一直知道寫(xiě)好測(cè)試用例是一個(gè)難度不亞于開(kāi)發(fā)的工作。測(cè)試用例有粒度問(wèn)題,我覺(jué)得,測(cè)試用例的粒度宜大不宜小。我這個(gè)項(xiàng)目是controller-service-mmodels架構(gòu),controller一個(gè)函數(shù)就是一個(gè)接口,service一個(gè)函數(shù)是一個(gè)通用性比較高的服務(wù),model是比較瘦的model,基本只做增刪改查。在我這個(gè)架構(gòu)中,我寫(xiě)的測(cè)試用例粒度大多數(shù)是controller級(jí)別的,有少數(shù)是service級(jí)別的,model級(jí)別的測(cè)試用例基本沒(méi)有。

測(cè)試用例粒度大一些,有個(gè)明顯的好處,就是對(duì)需求的容忍度高了很多。一般測(cè)試用例最痛的就是需求一旦修改了,我的業(yè)務(wù)邏輯就修改了,我的測(cè)試用例也要跟著修改。修改測(cè)試用例是很痛苦的事情。所以如果測(cè)試用例足夠大,比如和接口一樣大,那么基本上,由于業(yè)務(wù)接口的兼容性要求,我們的測(cè)試用例的輸入輸出一般不會(huì)進(jìn)行大的變動(dòng)(雖然里面的service或者model會(huì)進(jìn)行比較大的變動(dòng))。這樣有一些需求變化了之后,我甚至不需要修改任何測(cè)試用例的代碼就可以。

當(dāng)然有的測(cè)試用例粒度太大,一些小的分支可能就測(cè)試不到,或者很難構(gòu)建測(cè)試數(shù)據(jù),所以有的時(shí)候,還是需要一寫(xiě)稍微小一點(diǎn)的粒度的測(cè)試用例。

另外對(duì)于不需要依賴(lài)測(cè)試數(shù)據(jù)的類(lèi)庫(kù)函數(shù),如果你對(duì)這個(gè)類(lèi)庫(kù)函數(shù)的輸入輸出的需求變更有把握控制的話(huà),(你需要對(duì)自己的這個(gè)判斷負(fù)責(zé))這種類(lèi)庫(kù)函數(shù)的測(cè)試用例則是越細(xì)越好。

其他原則性的東西

說(shuō)說(shuō)寫(xiě)測(cè)試用例的一些原則性的東西。

檢驗(yàn)邏輯抗需求變更能力越強(qiáng)越好

首先,測(cè)試用例的檢驗(yàn)邏輯不是越全越好,而且有很多技巧。比如一個(gè)插入的接口,你測(cè)試是否插入成功,有很多時(shí)候,你根據(jù)判斷插入條數(shù)是否多一條會(huì)比你判斷這個(gè)插入條數(shù)的所有字段是否是你要求的更好。原則還是那個(gè),測(cè)試用例的抗需求變更能力會(huì)更高,首先基本上如果我的插入邏輯很簡(jiǎn)單,那么插入成功就約等于插入的每個(gè)字段都滿(mǎn)足,當(dāng)然這里是約等于,但是因?yàn)闃I(yè)務(wù)代碼也是我自己寫(xiě)的,心里這個(gè)B數(shù)還是有的。然后,如果一旦需求變更我這個(gè)數(shù)據(jù)多了一個(gè)字段,那么我這個(gè)測(cè)試用例基本不需要做任何修改就還可以繼續(xù)跑起來(lái)。

再次強(qiáng)調(diào)下,這里的約等于的判斷就是看你對(duì)你業(yè)務(wù)代碼的感覺(jué)了。

并不是所有的錯(cuò)誤都需要完美處理

測(cè)試代碼畢竟不像業(yè)務(wù)代碼那么需要完美的嚴(yán)謹(jǐn),所有的panic都是歡迎的。換句話(huà)說(shuō),我們業(yè)務(wù)代碼基本上對(duì)所有error都需要有所處理,但是測(cè)試用例并不一定了。如果我在上一行代碼中沒(méi)有處理這個(gè)error,那么我傳遞給下一行的參數(shù)很可能就是nil,很有可能在下一行代碼中直接panic了一個(gè)錯(cuò)誤出來(lái)。這個(gè)也能讓我發(fā)現(xiàn)我的錯(cuò)誤。

所以,測(cè)試用例并不需要寫(xiě)那么嚴(yán)謹(jǐn),有的地方直接panic錯(cuò)誤也是一個(gè)很好的選擇。

Fatal和Error的選擇

基本上我覺(jué)得Error沒(méi)啥用,我目前的測(cè)試用例都要求所有的判斷節(jié)點(diǎn)都跑成功,任何一個(gè)地方失敗了,直接就報(bào)錯(cuò)進(jìn)行調(diào)試。我的精力也不允許我一次性能處理多個(gè)錯(cuò)誤case,基本上調(diào)試失敗的測(cè)試用例是一個(gè)個(gè)調(diào)試的,所以error并沒(méi)有什么用。

這點(diǎn)純粹我個(gè)人觀點(diǎn),估計(jì)會(huì)有很多人不同意。

檢驗(yàn)邏輯多用變量

檢驗(yàn)邏輯盡量少用 response.Name == "測(cè)試路口" 這種代碼,能盡量找到替換"測(cè)試路口" 這個(gè)的變量盡量使用變量,同樣的理由,測(cè)試用例的抗需求變更能力會(huì)更高。

總結(jié)

測(cè)試用例是開(kāi)發(fā)人員最后一塊遮羞布,寫(xiě)Golang的代碼和寫(xiě)PHP的代碼確實(shí)體驗(yàn)完全不一樣,在Golang代碼中,首先寫(xiě)測(cè)試用例異常方便了。其次,Golang的調(diào)試成本遠(yuǎn)遠(yuǎn)高于PHP,寫(xiě)測(cè)試用例看起來(lái)是浪費(fèi)時(shí)間,實(shí)際上是節(jié)省你的調(diào)試時(shí)間。最后,golang代碼的每次重構(gòu)(增加一個(gè)字段,少一個(gè)字段)影響的文件數(shù)遠(yuǎn)遠(yuǎn)高于PHP,如果沒(méi)有這塊遮羞布,你怎么確保你的代碼修改后還能正常運(yùn)行呢?

Just Testing!

▌上圖

▌你可能對(duì)這些文章感興趣

?vimium使用

②?聊聊osm

③?golang的cms

④?一次composer錯(cuò)誤使用引發(fā)的思考

⑤?colly源碼學(xué)習(xí)

⑥?使用chan的時(shí)候選擇對(duì)象還是指針

⑦?golang中Context的使用場(chǎng)景

⑧?論凜冬

⑨?如何加速golang業(yè)務(wù)的開(kāi)發(fā)速度

總結(jié)

以上是生活随笔為你收集整理的golang 读取文件最后一行_测试用例是开发人员最后一块遮羞布的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。