導(dǎo)語(yǔ)非常幸運(yùn)的是,從4月份至今,我能夠全身心投入到騰訊新聞的單元測(cè)試專(zhuān)項(xiàng)任務(wù)中,從無(wú)知懵懂,到不斷深入理解的過(guò)程,與開(kāi)發(fā)同學(xué)互幫互助,受益匪淺。在此過(guò)程中,得到了質(zhì)量總監(jiān)、新聞總監(jiān)和喬幫主的傾囊指導(dǎo),真心感謝!!我希望把所有心得,總結(jié)成一篇較為全面的文章,分享給其他團(tuán)隊(duì)。時(shí)刻牢記:1. 不要濫用mock 2. 基于意圖。
在我們談到單元測(cè)試,大都清楚是測(cè)試函數(shù)符合預(yù)期,國(guó)外很多大公司都將單測(cè)執(zhí)行的很好,國(guó)內(nèi)成功的案例則相對(duì)有限。在本文中,筆者將在騰訊新聞項(xiàng)目中親身經(jīng)歷單測(cè)從無(wú)到有的實(shí)踐過(guò)程梳理為可讀可參考的經(jīng)驗(yàn)分享出來(lái)。在實(shí)踐的過(guò)程我發(fā)現(xiàn),單測(cè)可以推動(dòng)產(chǎn)品質(zhì)量轉(zhuǎn)為優(yōu)秀,推動(dòng)實(shí)行它的過(guò)程更需要對(duì)它有真實(shí)的認(rèn)識(shí)以及一套方法論。
為單元測(cè)試“正名”
我曾經(jīng)認(rèn)為,單元測(cè)試面向的是一個(gè)函數(shù)。任何走出一個(gè)函數(shù)的測(cè)試,都不是單元測(cè)試。
其實(shí),對(duì)“單元”的定義取決于自己。如果你正在使用函數(shù)式編程,一個(gè)單元最有可能指的是一個(gè)函數(shù)。你的單元測(cè)試將使用不同的參數(shù)調(diào)用這個(gè)函數(shù),并斷言它返回了期待的結(jié)果;在面向?qū)ο笳Z(yǔ)言里,下至一個(gè)方法,上至一個(gè)類(lèi)都可以是一個(gè)單元(從一個(gè)單一的方法到一整個(gè)的類(lèi)都可以是一個(gè)單元)。意圖很重要(“意圖”二字是本文中第一次提到,它很重要)
我們有單元測(cè)試、增量測(cè)試、集成測(cè)試、回歸測(cè)試、冒煙測(cè)試等等,名字非常多。谷歌看到這種“百家爭(zhēng)鳴”的現(xiàn)象,創(chuàng)立了自己的命名方式,只分為小型測(cè)試、中型測(cè)試和大型測(cè)試。
小型測(cè)試,針對(duì)單個(gè)函數(shù)的測(cè)試,關(guān)注其內(nèi)部邏輯,mock所有需要的服務(wù)。小型測(cè)試帶來(lái)優(yōu)秀的代碼質(zhì)量、良好的異常處理、優(yōu)雅的錯(cuò)誤報(bào)告
中型測(cè)試,驗(yàn)證兩個(gè)或多個(gè)制定的模塊應(yīng)用之間的交互
大型測(cè)試,也被稱(chēng)為“系統(tǒng)測(cè)試”或“端到端測(cè)試”。大型測(cè)試在一個(gè)較高層次上運(yùn)行,驗(yàn)證系統(tǒng)作為一個(gè)整體是如何工作的。
資源 | 小型測(cè)試 | 中型測(cè)試 | 大型測(cè)試 |
網(wǎng)絡(luò)訪問(wèn) | 否 | 僅訪問(wèn)localhost | 是 |
數(shù)據(jù)庫(kù)訪問(wèn) | 否 | 是 | 是 |
訪問(wèn)文件 | 否 | 是 | 是 |
訪問(wèn)用戶(hù)界面 | 否 | 否 | 是 |
使用外部服務(wù) | 否 | 不鼓勵(lì),可mock | 是 |
多線程 | 否 | 是 | 是 |
使用sleep語(yǔ)句 | 否 | 是 | 是 |
使用系統(tǒng)屬性設(shè)置 | 否 | 是 | 是 |
運(yùn)行時(shí)間限制(毫秒) | 60 | 300 | 900+ |
強(qiáng)制時(shí)間限制(分鐘) | 1 | 5 | 15 |
小型測(cè)試 | 中型測(cè)試 | 大型測(cè)試 | |
對(duì)應(yīng)測(cè)試類(lèi)型 | 單元測(cè)試 | 單元測(cè)試+邏輯層測(cè)試(泛?jiǎn)卧蚍謱訙y(cè)試) | UI測(cè)試或接口測(cè)試 |
結(jié)論:我們的單元測(cè)試,既可以針對(duì)一個(gè)函數(shù)寫(xiě)case,也可以按照函數(shù)的調(diào)用關(guān)系串起來(lái)寫(xiě)case。
金字塔模型
在金字塔模型之前,流行的是冰淇淋模型。包含了大量的手工測(cè)試、端到端的自動(dòng)化測(cè)試及少量的單元測(cè)試。造成的后果是,隨著產(chǎn)品壯大,手工回歸測(cè)試時(shí)間越來(lái)越長(zhǎng),質(zhì)量很難把控;自動(dòng)化case頻頻失敗,每一個(gè)失敗對(duì)應(yīng)著一個(gè)長(zhǎng)長(zhǎng)的函數(shù)調(diào)用,到底哪里出了問(wèn)題?單元測(cè)試少的可憐,基本沒(méi)作用。
Mike Cohn 在他的著作《Succeeding with Agile》一書(shū)中提出了“測(cè)試金字塔”這個(gè)概念。這個(gè)比喻非常形象,它讓你一眼就知道測(cè)試是需要分層的。它還告訴你每一層需要寫(xiě)多少測(cè)試。
測(cè)試金字塔本身是一條很好的經(jīng)驗(yàn)法則,我們最好記住Cohn在金字塔模型中提到的兩件事:
編寫(xiě)不同粒度的測(cè)試
層次越高,你寫(xiě)的測(cè)試應(yīng)該越少
同時(shí),我們對(duì)金字塔的理解絕不能止步于此,要進(jìn)一步理解:
我把金字塔模型理解為——冰激凌融化了。就是指,最頂部的“手工測(cè)試”理論上全部要自動(dòng)化,向下融化,優(yōu)先全部考慮融化成單元測(cè)試,單元測(cè)試覆蓋不了的 放在中間層(分層測(cè)試),再覆蓋不了的才會(huì)放到UI層。因此,UI層的case,能沒(méi)有就不要有,跑的慢還不穩(wěn)定。按照喬幫主的說(shuō)法,我不分單元測(cè)試還是分層測(cè)試,統(tǒng)一都叫自動(dòng)化測(cè)試,那就應(yīng)該把所有的自動(dòng)化case看做一個(gè)整體,case不要冗余,單元測(cè)試能覆蓋,就要把這個(gè)case從分層或ui中去掉。
越是底層的測(cè)試,牽扯到相關(guān)內(nèi)容越少,而高層測(cè)試則涉及面更廣。比如單元測(cè)試,它的關(guān)注點(diǎn)只有一個(gè)單元,而沒(méi)有其它任何東西。所以,只要一個(gè)單元寫(xiě)好了,測(cè)試就是可以通過(guò)的;而集成測(cè)試則要把好幾個(gè)單元組裝到一起才能測(cè)試,測(cè)試通過(guò)的前提條件是,所有這些單元都寫(xiě)好了,這個(gè)周期就明顯比單元測(cè)試要長(zhǎng);系統(tǒng)測(cè)試則要把整個(gè)系統(tǒng)的各個(gè)模塊都連在一起,各種數(shù)據(jù)都準(zhǔn)備好,才可能通過(guò)。
另外,因?yàn)樯婕暗降哪K過(guò)多,任何一個(gè)模塊做了調(diào)整,都有可能破壞高層測(cè)試,所以,高層測(cè)試通常是相對(duì)比較脆弱的,在實(shí)際的工作中,有些高層測(cè)試會(huì)牽扯到外部系統(tǒng),這樣一來(lái),復(fù)雜度又在不斷地提升。
為什么做單測(cè)
這個(gè)問(wèn)題我們規(guī)避不掉。新聞是這次研發(fā)模式改革的主力軍之一,所以自上而下的推動(dòng)讓這個(gè)問(wèn)題不那么棘手:做了就是做了。不做,卻又有那么多的理由:(搜集到的吐槽真實(shí)聲音)
單元測(cè)試?yán)速M(fèi)了太多的時(shí)間
單元測(cè)試僅僅是證明這些代碼做了什么
我是很棒的程序員,我是不是可以不進(jìn)行單元測(cè)試?
后面的集成測(cè)試將會(huì)抓住所有的bug
單元測(cè)試的成本效率不高我把測(cè)試都寫(xiě)了,那么測(cè)試人員做什么呢?
公司請(qǐng)我來(lái)是寫(xiě)代碼,而不是寫(xiě)測(cè)試
測(cè)試代碼的正確性,并不是我的工作
我覺(jué)得我們總監(jiān)指導(dǎo)的很到位:改革,一是工作方式的改革,更難的是思想上的改革。
單元測(cè)試的意義
新聞的總監(jiān)dot老師是至始至終推進(jìn)單測(cè)的好領(lǐng)導(dǎo),他講述了螺絲釘與飛機(jī)的故事:干貨 | 測(cè)試扁平化之必備神器:好的單元測(cè)試
單元測(cè)試對(duì)我們的產(chǎn)品質(zhì)量是非常重要的。
單元測(cè)試是所有測(cè)試中最底層的一類(lèi)測(cè)試,是第一個(gè)環(huán)節(jié),也是最重要的一個(gè)環(huán)節(jié),是唯一一次有保證能夠代碼覆蓋率達(dá)到100%的測(cè)試,是整個(gè)軟件測(cè)試過(guò)程的基礎(chǔ)和前提,單元測(cè)試防止了開(kāi)發(fā)的后期因bug過(guò)多而失控,單元測(cè)試的性?xún)r(jià)比是最好的。
據(jù)統(tǒng)計(jì),大約有80%的錯(cuò)誤是在軟件設(shè)計(jì)階段引入的,并且修正一個(gè)軟件錯(cuò)誤所需的費(fèi)用將隨著軟件生命期的進(jìn)展而上升。錯(cuò)誤發(fā)現(xiàn)的越晚,修復(fù)它的費(fèi)用就越高,而且呈指數(shù)增長(zhǎng)的趨勢(shì)。作為編碼人員,也是單元測(cè)試的主要執(zhí)行者,是唯一能夠做到生產(chǎn)出無(wú)缺陷程序這一點(diǎn)的人,其他任何人都無(wú)法做到這一點(diǎn)
代碼規(guī)范、優(yōu)化,可測(cè)試性的代碼
放心重構(gòu)
自動(dòng)化執(zhí)行three-thousand times
下面這張圖,來(lái)自微軟的統(tǒng)計(jì)數(shù)據(jù):bug在單元測(cè)試階段被發(fā)現(xiàn),平均耗時(shí)3.25小時(shí),如果漏到系統(tǒng)測(cè)試階段,要花費(fèi)11.5小時(shí)。
下面這張圖,旨在說(shuō)明兩個(gè)問(wèn)題:85%的缺陷都在代碼設(shè)計(jì)階段產(chǎn)生,而發(fā)現(xiàn)bug的階段越靠后,耗費(fèi)成本就越高,指數(shù)級(jí)別的增高。所以,在早期的單元測(cè)試就能發(fā)現(xiàn)bug,省時(shí)省力,一勞永逸,何樂(lè)而不為呢
單元測(cè)試特別耗時(shí)?
不能一刀切,不能只盯著單測(cè)階段的耗時(shí)。
我采訪了新聞客戶(hù)端、后臺(tái)的開(kāi)發(fā),首先肯定的是,單測(cè)會(huì)增加開(kāi)發(fā)量、增加開(kāi)發(fā)時(shí)長(zhǎng)。
在《單元測(cè)試的藝術(shù)》這本書(shū)提到一個(gè)案例:找了開(kāi)發(fā)能力相近的兩個(gè)團(tuán)隊(duì),同時(shí)開(kāi)發(fā)相近的需求。進(jìn)行單測(cè)的團(tuán)隊(duì)在編碼階段時(shí)長(zhǎng)增長(zhǎng)了一倍,從7天到14天,但是,這個(gè)團(tuán)隊(duì)在集成測(cè)試階段的表現(xiàn)非常順暢,bug量小,定位bug迅速等。最終的效果,整體交付時(shí)間和缺陷數(shù),均是單測(cè)團(tuán)隊(duì)最少。
單測(cè),存在即合理。一方面,需要把單測(cè)放在整個(gè)迭代周期來(lái)觀測(cè)其效果;一方面,寫(xiě)單測(cè)也是技術(shù)活,寫(xiě)得好的同學(xué),時(shí)間少代碼質(zhì)量高(也即,不是說(shuō)寫(xiě)了單測(cè),就能寫(xiě)好單測(cè))
誰(shuí)來(lái)寫(xiě)單測(cè)呢?
開(kāi)發(fā)同學(xué)寫(xiě)單測(cè)
測(cè)試同學(xué)具有寫(xiě)單測(cè)的能力。重點(diǎn)在于開(kāi)發(fā)腳手架、分層測(cè)試/端到端測(cè)試
增量還是存量
單測(cè)case針對(duì)增量代碼
當(dāng)存量代碼出現(xiàn)大規(guī)模重構(gòu),后者質(zhì)量暴露出極大風(fēng)險(xiǎn)時(shí),都是推動(dòng)補(bǔ)全單測(cè)的好時(shí)機(jī)
單元測(cè)試的階段
一. 廣義的單元測(cè)試,我們指這三部分的有機(jī)組合:
code review
靜態(tài)代碼掃描
單元測(cè)試用例編寫(xiě)
二. 結(jié)合新聞的實(shí)踐,我把單測(cè)成長(zhǎng)的過(guò)程分為4個(gè)目標(biāo),分別為:
會(huì)寫(xiě),全員可寫(xiě)
寫(xiě)的好,同時(shí)關(guān)注可測(cè)性問(wèn)題,試點(diǎn)解決
識(shí)別可測(cè)性問(wèn)題,熟練使用重構(gòu)方法進(jìn)行重構(gòu);識(shí)別代碼架構(gòu)設(shè)計(jì)問(wèn)題;case與業(yè)務(wù)代碼同步編寫(xiě)
TDD。但這個(gè)目標(biāo)是期望,不能作為必須實(shí)現(xiàn)的目標(biāo)。
截至發(fā)稿當(dāng)天,新聞處于第三階段,即,每個(gè)迭代均能產(chǎn)出高質(zhì)量的case,人數(shù)覆蓋和需求覆蓋均較高;關(guān)注重點(diǎn)在于可測(cè)性,時(shí)刻注重重構(gòu)。
單元測(cè)試的指標(biāo)
還挺尷尬的,不太有直接的指標(biāo)去衡量單測(cè)的效果。我們也經(jīng)常被問(wèn)到,“怎么證明你們新聞單測(cè)的作用呀?”
bug類(lèi)指標(biāo)(間接指標(biāo)):連續(xù)迭代的bug總數(shù)趨勢(shì)、迭代內(nèi)新建bug的趨勢(shì)、千行bug率
單測(cè)的需求覆蓋度(50%以上),參與人員覆蓋度(80%以上)
單測(cè)case總數(shù)趨勢(shì),代碼行增量趨勢(shì)
增量代碼的行覆蓋率(接入層80%,客戶(hù)端30%)
單函數(shù)圈復(fù)雜度(低于40),單函數(shù)代碼行數(shù)(低于80),掃描告警數(shù)
在迭代需求持續(xù)高吞吐量的前提下,以新聞iOS的數(shù)據(jù)為例:
go單元測(cè)試框架選型
基本選型:testify + gomonkey
附加:httptest + sqlmock
前提
測(cè)試文件,以_test.go結(jié)尾,與被測(cè)文件放于相同目錄
測(cè)試函數(shù),函數(shù)名以Test開(kāi)頭,并且隨后的第一個(gè)字符必須為大寫(xiě)字母或下劃線,如:TestParseReq_CorrectNum_TableDriven
測(cè)試函數(shù),參數(shù)為t *testing.T;對(duì)于bench測(cè)試,參數(shù)為b *testing.B
運(yùn)行命令行,我的文章有深入講解:go test命令行
testify常規(guī)用法
https://github.com/stretchr/testify
testify基于gotesting編寫(xiě),所以語(yǔ)法上、執(zhí)行命令行與go test完全兼容
支持大量高效的api,比如:
assert.Equal:常規(guī)對(duì)比,是把兩者分別換成[]byte去嚴(yán)格比對(duì) assert.Nil:判斷對(duì)象為nil時(shí),有時(shí)對(duì)err判空時(shí)也用 assert.Error:判斷err的具體類(lèi)型和內(nèi)容 assert.JSONEq:這個(gè)比較有用,對(duì)比map時(shí);或者對(duì)比struct的時(shí)候,也會(huì)先轉(zhuǎn)為map,在用這個(gè)api去做對(duì)比,如下面這個(gè)例子,我封裝了建議的方法去將struct轉(zhuǎn)換為string(json):
支持suite,用例集管理
運(yùn)行時(shí),可以指定用例集執(zhí)行
自帶mock工具,但只支持接口方法的mock,而且用法相對(duì)復(fù)雜
table-driven
gomonkey用法(加粗字體表示常用)
https://github.com/agiledragon/gomonkey
https://studygolang.com/articles/15034
支持為一個(gè)函數(shù)打一個(gè)樁
支持為一個(gè)成員方法打一個(gè)樁
支持為一個(gè)全局變量打一個(gè)樁
支持為一個(gè)函數(shù)變量打一個(gè)樁
支持為一個(gè)函數(shù)打一個(gè)特定的樁序列
支持為一個(gè)成員方法打一個(gè)特定的樁序列
支持為一個(gè)函數(shù)變量打一個(gè)特定的樁序列
table-driven的方式定義一系列stub
注意,對(duì)內(nèi)聯(lián)函數(shù)的Stub,go test命令行一定要加上參數(shù)才可生效。見(jiàn)官方文檔。所以,我的命令行默認(rèn)加上-gcflags=all=-l就行了。
我設(shè)置了一些goland的代碼模板,放在附件中。
ApplyFunc是對(duì)外部函數(shù)Stub(非類(lèi)方法)
/* 用法:gomonkey.ApplyFunc(被stub函數(shù)名, 被stub函數(shù)簽名) 函數(shù)返回值 *例子: patches := gomonkey.ApplyFunc(fake.Exec, func(_ string, _ ...string) (string, error) { return outputExpect, nil })*/ patches := gomonkey.ApplyFunc(lcache.GetCache, func(_ string) (interface{}, bool) { return getCommentsResp() })defer patches.Reset()
(左滑可查看完整代碼,下同)
ApplyMethod是對(duì)類(lèi)函數(shù)Stub。但這里注意,要被stub的方式是私有方法,gomonkey通過(guò)反射是找不到的,有兩種解決方法:
1)使用增強(qiáng)版的gomonkey;
2)不Stub它,而是選擇走進(jìn)這個(gè)函數(shù),這個(gè)話題在后面專(zhuān)題談mock的時(shí)候說(shuō)。
/* 用法:gomonkey.ApplyMethod(反射類(lèi)名, 被stub函數(shù)簽名) 函數(shù)返回值 *例子: var s *fake.Slice patches := ApplyMethod(reflect.TypeOf(s), "Add", func(_ *fake.Slice, _ int) error { return nil })*/ var ac *auth.AuthCheckpatches := gomonkey.ApplyMethod(reflect.TypeOf(ac), "PrepareWithHttp", func(_ *auth.AuthCheck, _ *http.Request, _ ...auth.AuthOption) error { return fmt.Errorf("prepare with nil object") })defer patches.Reset()
ApplyMethodSeq是對(duì)同一個(gè)Stub的函數(shù)返回不同的結(jié)果
/* 用法:gomonkey.ApplyMethodSeq(類(lèi)的反射,"被stub函數(shù)名", 返回結(jié)構(gòu)體); Params{info1},中括號(hào)內(nèi)為被stub函數(shù)的返回值列表; Times為生效次數(shù) *例子: e := &fake.Etcd{} info1 := "hello cpp" info2 := "hello golang" info3 := "hello gomonkey" outputs := []OutputCell{ {Values: Params{info1, nil}}, {Values: Params{info2, nil}}, {Values: Params{info3, nil}}, } patches := ApplyMethodSeq(reflect.TypeOf(e), "Retrieve", outputs) defer patches.Reset()*/conn := &redis.RedisConn{}patch1 := gomonkey.ApplyFunc(redis.NewRedisHTTP, func(serviceName string, _ string) *redis.RedisConn { conn := &redis.RedisConn{ redis.RedisConfig{}, &redis.RedisHelper{}, } return conn }) defer patch1.Reset() // mock redis data. 返回空和不為空的情況 outputCell := []gomonkey.OutputCell{ {Values: gomonkey.Params{"12", nil}, Times: 1}, {Values: gomonkey.Params{"", nil}, Times: 1}, }patchs := gomonkey.ApplyMethodSeq(reflect.TypeOf(conn.RedisHelper), "Get", outputCell)defer patchs.Reset()
先舉這幾個(gè)例子,詳細(xì)的可以在上面的鏈接文章中全面得到。
這里補(bǔ)充一點(diǎn),對(duì)類(lèi)方法進(jìn)行stub,必須要找到該方法對(duì)應(yīng)的真實(shí)的類(lèi)(結(jié)構(gòu)體),舉個(gè)例子:
//被測(cè)函數(shù)中有如下一段,其中的Get方法我們想stub掉,只要找到Get方法對(duì)應(yīng)的類(lèi)就好了readCountStr, _ := conn.Get(redisKey)if len(readCountStr) == 0 { return 0, nil }
定位conn,是RedisConn類(lèi)型的structtype RedisConn struct { RedisConfig *RedisHelper} 所以第一次,我用gomonkey.AppleyMethod時(shí)這么寫(xiě): patches := gomonkey.ApplyMethod(reflect.TypeOf(*RedisConn),"Get", func(_ *redis.RedisHelper,_ string, _ []string) ([]string, error){ return info,err_notNil })deferpatches.Reset()
運(yùn)行時(shí)報(bào)了空指針panic,提示RedisConn沒(méi)有Get方法。
繼續(xù)追,原來(lái)Get是*RedisHelper的方法,組合到了RedisConn結(jié)構(gòu)體中,共用方法。但我們使用gomonkey時(shí),需要指向真正定義它的類(lèi)
func (this *RedisHelper) Get(key string) (string, error) { return redigo.String(this.Do("GET", key))
最終這么寫(xiě):
patches := gomonkey.ApplyMethod(reflect.TypeOf(giftData.rankRedisRD.RedisHelper),"Get", func(_ *redis.RedisHelper,_ string, _ []string) ([]string, error){ return info,err_notNil })defer patches.Reset()
必須說(shuō)一說(shuō)mock了
test doubles
在《xUnit Test Patterns》一書(shū)中,作者首次提出test doubles(測(cè)試替身)的概念。我們常掛在嘴邊的mock只是其中一種,而且是最容易與Stub(打樁)混淆的一種。在上一節(jié)中對(duì)gomonkey的介紹,你可以注意到了,我沒(méi)有使用mock,全部是Stub。是的,gomonkey不是mock工具,只是一個(gè)高級(jí)打樁的工具,適配了我們大部分的使用場(chǎng)景。
測(cè)試替身,共有五種:可以參考這篇翻譯《xUnit Test Patterns》學(xué)習(xí)筆記6 - Test Double
Dummy Object:
用于傳遞給調(diào)用者但是永遠(yuǎn)不會(huì)被真實(shí)使用的對(duì)象,通常它們只是用來(lái)填滿(mǎn)參數(shù)列表
Test Stub
Stubs通常用于在測(cè)試中提供封裝好的響應(yīng),譬如有時(shí)候編程設(shè)定的并不會(huì)對(duì)所有的調(diào)用都進(jìn)行響應(yīng)。Stubs也會(huì)記錄下調(diào)用的記錄,譬如一個(gè)email gateway就是一個(gè)很好的例子,它可以用來(lái)記錄所有發(fā)送的信息或者它發(fā)送的信息的數(shù)目。簡(jiǎn)而言之,Stubs一般是對(duì)一個(gè)真實(shí)對(duì)象的封裝
Test Spy
Test Spy像一個(gè)間諜,安插在了SUT內(nèi)部,專(zhuān)門(mén)負(fù)責(zé)將SUT內(nèi)部的間接輸出(indirect outputs)傳到外部。它的特點(diǎn)是將內(nèi)部的間接輸出返回給測(cè)試案例,由測(cè)試案例進(jìn)行驗(yàn)證,Test Spy只負(fù)責(zé)獲取內(nèi)部情報(bào),并把情報(bào)發(fā)出去,不負(fù)責(zé)驗(yàn)證情報(bào)的正確性
Mock Object
針對(duì)設(shè)定好的調(diào)用方法與需要響應(yīng)的參數(shù)封裝出合適的對(duì)象
Fake Object
Fake對(duì)象常常與類(lèi)的實(shí)現(xiàn)一起起作用,但是只是為了讓其他程序能夠正常運(yùn)行,譬如內(nèi)存數(shù)據(jù)庫(kù)就是一個(gè)很好的例子。
stub與mock
打樁和mock應(yīng)該是最容易混淆的,而且習(xí)慣上我們統(tǒng)一用mock去形容模擬返回的能力,習(xí)慣成自然,也就把mock常掛在嘴邊了。
就我的理解,stub可以理解為mock的子集,mock更強(qiáng)大一些:
mock可以驗(yàn)證實(shí)現(xiàn)過(guò)程,驗(yàn)證某個(gè)函數(shù)是否被執(zhí)行,被執(zhí)行幾次
mock可以依條件生效,比如傳入特定參數(shù),才會(huì)使mock效果生效
mock可以指定返回結(jié)果
當(dāng)mock指定任何參數(shù)都返回固定的結(jié)果時(shí),它等于stub
只不過(guò),go的mock工具gomock只基于接口生效,不適合新聞、企鵝號(hào)項(xiàng)目,而gomonkey的stub覆蓋了大部分的使用場(chǎng)景。
不要濫用mock
我把這一部分單獨(dú)放一章節(jié),表現(xiàn)出它重要的意義。需要讀懂肖鵬的《mock七宗罪》,在gitchat上。
兩個(gè)門(mén)派
約從2004-2005年間,江湖上形成兩大門(mén)派:經(jīng)典測(cè)試驅(qū)動(dòng)開(kāi)發(fā)派 和 mockist(mock極端派)。
先說(shuō)mockist。他主張將被測(cè)函數(shù)所有調(diào)用的外面函數(shù),全部mock。也即,只關(guān)注被測(cè)函數(shù)自己的一行行代碼,只要調(diào)用其他函數(shù),全都mock掉,用假數(shù)據(jù)來(lái)測(cè)試。
再說(shuō)經(jīng)典測(cè)試驅(qū)動(dòng)開(kāi)發(fā)派,他們主張不要濫用mock,能不mock就不mock,被測(cè)單元也不一定是具體的一個(gè)函數(shù),可能是多個(gè)函數(shù),串起來(lái)。必要的時(shí)候再mock。
兩個(gè)門(mén)派相爭(zhēng)多年,理論各有利弊,至今仍然共存。存在即合理。比如mockist,使用了過(guò)多的mock,無(wú)法覆蓋函數(shù)接口,這部分又是很容易出錯(cuò)的;經(jīng)典派,串的太多,又被質(zhì)疑是集成測(cè)試。
對(duì)于我們實(shí)際應(yīng)用,不必強(qiáng)制遵從某一派,結(jié)合即可,需要的時(shí)候mock,盡量少mock,不用糾結(jié)。
什么時(shí)候適合mock
如果一個(gè)對(duì)象具有以下特征,比較適合使用mock對(duì)象:
該對(duì)象提供非確定的結(jié)果(比如當(dāng)前的時(shí)間或者當(dāng)前的溫度)
對(duì)象的某些狀態(tài)難以創(chuàng)建或者重現(xiàn)(比如網(wǎng)絡(luò)錯(cuò)誤或者文件讀寫(xiě)錯(cuò)誤)
對(duì)象方法上的執(zhí)行太慢(比如在測(cè)試開(kāi)始之前初始化數(shù)據(jù)庫(kù))
該對(duì)象還不存在或者其行為可能發(fā)生變化(比如測(cè)試驅(qū)動(dòng)開(kāi)發(fā)中驅(qū)動(dòng)創(chuàng)建新的類(lèi))
該對(duì)象必須包含一些專(zhuān)門(mén)為測(cè)試準(zhǔn)備的數(shù)據(jù)或者方法(后者不適用于靜態(tài)類(lèi)型的語(yǔ)言,流行的Mock框架不能為對(duì)象添加新的方法。Stub是可以的。)
因此,不要濫用mock(stub),當(dāng)被測(cè)方法中調(diào)用其他方法函數(shù),第一反應(yīng)應(yīng)該走進(jìn)去串起來(lái),而不是從根部就mock掉了。
用例設(shè)計(jì)法
喬幫主介紹了一篇文章:像機(jī)器一樣思考
文章講述思考程序設(shè)計(jì)的根本思路——考慮輸入輸出。我們?cè)O(shè)計(jì)case,想要得到最全面的設(shè)計(jì),根本是考慮全輸入全輸出的組合,當(dāng)然,一方面,這么做耗時(shí)太大,很多時(shí)候是不可執(zhí)行的;一方面,這不是想要的結(jié)果,要考慮投入產(chǎn)出比。這時(shí),需要理論與實(shí)踐相結(jié)合,理論指導(dǎo)實(shí)踐,實(shí)踐精細(xì)理論。
先說(shuō)理論
1. 還是從上篇文章說(shuō)起,考慮輸入、輸出,就要先知道哪些屬于輸入輸出:
2. 白盒&黑盒設(shè)計(jì)
白盒法:
邏輯覆蓋(語(yǔ)句、分支、條件、條件組合等)
路徑(全路徑、最小線性無(wú)關(guān)路徑)
循環(huán):結(jié)合5種場(chǎng)景(跳過(guò)循環(huán)、循環(huán)一次,循環(huán)最大次,循環(huán)m次命中、循環(huán)m次未命中)
黑盒法: 等價(jià)類(lèi):正確的,錯(cuò)誤的(合法的,非法的) 邊界法:[1,10] ==> 0,1,2,9,10,11(是等價(jià)類(lèi)的有效補(bǔ)充)
3. 結(jié)合應(yīng)用
全輸入輸出,實(shí)施難度較大,轉(zhuǎn)而我們思考到業(yè)內(nèi)大神們?cè)O(shè)計(jì)出白盒黑盒設(shè)計(jì)法,通過(guò)仔細(xì)思考,可以判斷出是對(duì)全輸入全輸出的方法論體現(xiàn)。
因此,白盒&黑盒用例設(shè)計(jì)法,每一種我都親自實(shí)踐,理解其優(yōu)缺點(diǎn),從設(shè)計(jì)覆蓋角度,條件組合>最小線性無(wú)關(guān)路徑>條件>分支>語(yǔ)句。
下面這張圖,是我早期思考用例設(shè)計(jì)時(shí)的一次實(shí)踐,現(xiàn)在回憶起來(lái),它過(guò)度設(shè)計(jì)了。
但實(shí)際中,我們擔(dān)心“過(guò)度設(shè)計(jì)”,也還無(wú)法給出答案“用什么方法設(shè)計(jì)保證萬(wàn)無(wú)一失”。
過(guò)度設(shè)計(jì),也會(huì)使case脆弱
在有限的時(shí)間內(nèi),我們尋求收益較大化
1. 小函數(shù)&重要(計(jì)算,對(duì)象處理):盡量設(shè)計(jì)全面
2. 邏輯較重,代碼行數(shù)較多:分支、語(yǔ)句覆蓋 + 循環(huán) + 典型的邊界處理(我們看個(gè)例子:GetUserGiftList)
3. 引出“基于實(shí)現(xiàn)”與“基于意圖”的設(shè)計(jì):過(guò)多去Stub被測(cè)函數(shù)內(nèi)部的調(diào)用,就越接近“基于實(shí)現(xiàn)”(第二次提到“基于意圖”)
基于意圖與基于實(shí)現(xiàn)
這個(gè)話題是非常重要的。
基于意圖:思考函數(shù)最終想做什么,把被測(cè)函數(shù)當(dāng)做黑盒,考慮其輸出輸出,而不要關(guān)注其中間是怎樣實(shí)現(xiàn)的,究竟生成了什么臨時(shí)變量,循環(huán)了幾次,有什么判斷等。
基于實(shí)現(xiàn):輸入輸出我也考慮,中間怎么實(shí)現(xiàn)的我也考慮。mock就是一個(gè)好例子,比如我們寫(xiě)一個(gè)case,我們會(huì)用mock去驗(yàn)證函數(shù)內(nèi)是否調(diào)用了哪個(gè)外部方法、調(diào)用了幾次,語(yǔ)句的執(zhí)行順序是怎樣的。程序的變動(dòng)比需求還快,重構(gòu)隨時(shí)都有,稍有一變,case大批量失敗,這也是《mock七宗罪》中提到的一種情況。
我們要的是基于意圖,遠(yuǎn)離基于實(shí)現(xiàn)。
dot老師和喬幫主給我們上了課程,結(jié)合實(shí)戰(zhàn)經(jīng)驗(yàn),我總結(jié)如下:
“要么寫(xiě)好,要么不寫(xiě)”。case也是代碼,也需要維護(hù),也有工作量,所以要寫(xiě)的到位,而不是寫(xiě)得多。寫(xiě)了一堆沒(méi)用的,你還得維護(hù),不如刪了。
拿到一個(gè)函數(shù),先問(wèn)問(wèn)自己,這個(gè)函數(shù)要實(shí)現(xiàn)什么功能,最終輸出是什么;然后,問(wèn)自己,這個(gè)函數(shù)的風(fēng)險(xiǎn)在哪里,哪部分邏輯不太自信,最容易出錯(cuò)(計(jì)算、復(fù)雜的判斷、某異常分支的命中等)。這些才是我們case要覆蓋的點(diǎn)。
內(nèi)聯(lián)函數(shù)、直接get/set,沒(méi)幾行沒(méi)什么邏輯的,只要你判斷沒(méi)什么風(fēng)險(xiǎn),就不用寫(xiě)case。
確定了要寫(xiě)的case,再用分支條件組合、邊界等核心方面設(shè)計(jì)出具體用例,實(shí)施編寫(xiě)。
可以結(jié)合新聞幾次單測(cè)case review記錄,來(lái)詳細(xì)理解。詳見(jiàn)我的KM文章
我們看一個(gè)具體的case:
拿到這個(gè)函數(shù),作為測(cè)試同學(xué)的我先向開(kāi)發(fā)了解該函數(shù)的意圖:對(duì)符合格式、符合時(shí)間的用戶(hù)禮物進(jìn)行加和
讀代碼,了解了代碼流程、幾個(gè)異常分支,先做了code review
根據(jù)必要的異常分支,設(shè)計(jì)case覆蓋
對(duì)正常的業(yè)務(wù)流程,是按照開(kāi)發(fā)講述的函數(shù)意圖,進(jìn)行設(shè)計(jì),case如下:
被測(cè)函數(shù):
ret := make(map[int]int) now := library.UnixNow() for record, numStr := range giftRecord { hasNum, err := strconv.Atoi(numStr) if err != nil || hasNum < 0 { continue } detail := strings.Split(record, ":") if len(detail) != 2 { continue } itemExpire, err := strconv.ParseInt(detail[1], 10, 64) if err != nil { continue } //星星過(guò)期 if itemExpire != 0 && now > itemExpire { continue } //統(tǒng)計(jì)可用數(shù)目 giftId, err := strconv.Atoi(detail[0]) if err != nil { continue } if _, ok := ret[giftId]; !ok { ret[giftId] = hasNum } else { ret[giftId] += hasNum }}
正常路徑的單測(cè)case
func TestNum_CorrectRet(t *testing.T) { giftRecord := map[string]string{ "1:1000": "10", "1:2001": "100", "1:999": "20", "2": "200", "a": "30", "2:1001": "20", "2:999": "200",} expectRet := map[int]int{ 1: 110, 2: 20, } var s *redis.xxx patches := gomonkey.ApplyMethod(reflect.TypeOf(s), "Getxxx", func(_ *redis.xxx, _ string)(map[string]string, error) { return giftRecord, nil })deferpatches.Reset() p := &StarData{xxx }userStarNum,err:=p.GetNum(10000) assert.Nil(t, err)assert.JSONEq(t,Calorie.StructToString(expectRet),Calorie.StructToString(userStarNum))}
有同學(xué)會(huì)問(wèn)到:但是你最終還是看的代碼呀?看到代碼的正確邏輯是怎么處理的,再去設(shè)計(jì)的case和構(gòu)造數(shù)據(jù)吧?而且你不看代碼,怎么知道有哪些異常分支要覆蓋呢?
答:1. 我現(xiàn)在作為測(cè)試同學(xué)寫(xiě)開(kāi)發(fā)同學(xué)的case,確實(shí)需要知道有哪些異常分支要處理, 但不局限于代碼中的幾種,還應(yīng)該包括我理解到的異常分支,都要體現(xiàn)在case中。我們的case絕不是為了證明代碼是怎么實(shí)現(xiàn)的!通過(guò)單測(cè),我們經(jīng)常能夠發(fā)現(xiàn)bug。但是將來(lái)是開(kāi)發(fā)來(lái)寫(xiě)單測(cè)的,他自己設(shè)計(jì)的函數(shù)肯定知道要覆蓋哪些異常分支。
2. 嗯,我需要看代碼的正常流程是怎樣的,但不代表著把代碼扒下來(lái)以設(shè)計(jì)出case。case實(shí)際上是通過(guò)與開(kāi)發(fā)的溝通后,了解輸入數(shù)據(jù)的結(jié)構(gòu),輸出的格式,數(shù)據(jù)校驗(yàn)和計(jì)算的過(guò)程,去設(shè)計(jì)輸入輸出的。
用例編寫(xiě)的策略
對(duì)于怎么個(gè)順序去寫(xiě)單測(cè),我們重點(diǎn)實(shí)踐了一番,基本上也就三種情況吧:
獨(dú)立原子:mockist,被我們推翻了。當(dāng)然,最底部的函數(shù)可能沒(méi)有外部依賴(lài),那單測(cè)它就夠了。
自上而下(紅線):從入口函數(shù)往下測(cè)。實(shí)踐的過(guò)程中,我發(fā)現(xiàn)很難執(zhí)行,因?yàn)槲覐娜肟谔幘鸵牒妹恳淮握{(diào)用都需要返回哪些數(shù)據(jù)及格式,串起來(lái)一個(gè)case已經(jīng)非常不易。
自下而上(黃線):我們發(fā)現(xiàn),入口函數(shù),往往沒(méi)什么邏輯,調(diào)用另一個(gè)函數(shù)然后拿到響應(yīng)返回。所以入口函數(shù),也許不用寫(xiě)?我們繼續(xù)往下看,每一次調(diào)用的函數(shù)都看,也調(diào)出了以往的線上線下bug,我們發(fā)現(xiàn)出現(xiàn)問(wèn)題的代碼部分往往是調(diào)用鏈的底端,尤其是涉及計(jì)算、復(fù)雜分支循環(huán)等。而且,底端的函數(shù)往往可測(cè)性較好。
因此,考慮兩方面,我們選擇自下而上設(shè)計(jì)來(lái)選擇函數(shù)編寫(xiě)case:
底部的函數(shù)可測(cè)性通常很好
核心邏輯比較多,尤其涉及計(jì)算、拼接,分支的。
可測(cè)性問(wèn)題的解決——重構(gòu)
導(dǎo)致無(wú)法寫(xiě)單測(cè)的重要原因是,代碼可測(cè)性不好。如果一個(gè)函數(shù)八九十行、二三百行,基本就是不可測(cè)的,或者說(shuō)“不好測(cè)的”。因?yàn)槔锩孢壿嬏嗔耍瑥牡谝恍械阶詈笠恍卸冀?jīng)歷了什么,各種函數(shù)調(diào)用外部依賴(lài),各種if/for,各種異常分支處理,寫(xiě)一個(gè)case的代碼行數(shù)可能是原函數(shù)的幾倍。
因此,推動(dòng)單測(cè)走下去,重構(gòu)提升可測(cè)性是必須環(huán)節(jié)。而且,通過(guò)重構(gòu),代碼結(jié)構(gòu)間接清晰了,更可讀可維護(hù),更容易發(fā)現(xiàn)和定位問(wèn)題。
常見(jiàn)的問(wèn)題:重復(fù)代碼、魔法數(shù)字、箭頭式的代碼等
推薦的理論書(shū)籍是《重構(gòu):改善既有代碼的設(shè)計(jì)》第二版、《clean code》
我輸出了一篇關(guān)于重構(gòu)的文章。
使用codecc(騰訊代碼檢查中心)的圈復(fù)雜度、函數(shù)長(zhǎng)度來(lái)評(píng)估代碼結(jié)構(gòu)質(zhì)量,我們與開(kāi)發(fā)一起學(xué)習(xí),一起實(shí)踐,不斷有成果輸出。
對(duì)于箭頭式的代碼,可考慮如下步驟:
多使用衛(wèi)語(yǔ)句,先判斷異常,異常return
將判斷語(yǔ)句抽離
將核心部分抽離為函數(shù)
用例維護(hù),可讀性、可維護(hù)性、可信賴(lài)性
用例設(shè)計(jì)要素
將內(nèi)部邏輯與外部請(qǐng)求分開(kāi)測(cè)試
對(duì)服務(wù)邊界(interface)的輸入和輸出進(jìn)行嚴(yán)格驗(yàn)證
用斷言來(lái)代替原生的報(bào)錯(cuò)函數(shù)
避免隨機(jī)結(jié)果
盡量避免斷言時(shí)間的結(jié)果
適時(shí)使用setup和teardown
測(cè)試用例之間相互隔離,不要相互影響
原子性,所有的測(cè)試只有兩種結(jié)果:成功和失敗
避免測(cè)試中的邏輯,即不該包含if、switch、for、while等
不要保護(hù)起來(lái),try…catch…
每個(gè)用例只測(cè)試一個(gè)關(guān)注點(diǎn)
少用sleep,延緩測(cè)試時(shí)長(zhǎng)的行為都是不健康的
3A策略:arrange,action,assert
用例可讀性
標(biāo)題要明確表明意圖,如Test+被測(cè)函數(shù)名+condition+result。case失敗后,通過(guò)名字就知道哪個(gè)場(chǎng)景失敗,而不用一行行再讀代碼。將來(lái)維護(hù)這個(gè)測(cè)試代碼的,可能是其他人,我們需要讓別人容易讀懂
測(cè)試代碼的內(nèi)容要清晰,3A原則:arrange,action,assert 分成三部分。數(shù)據(jù)準(zhǔn)備部分arrange如果代碼行較多,考慮抽離出去。
斷言的意圖明顯,可以考慮將魔法數(shù)字變?yōu)樽兞浚ㄋ滓淄?/p>
一個(gè)case,不要做過(guò)多的assert,要專(zhuān)一
和業(yè)務(wù)代碼的要求一致,都要可讀
用例可維護(hù)性
重復(fù):文本字符串重復(fù)、結(jié)構(gòu)重復(fù)、語(yǔ)義重復(fù)
拒絕硬編碼
基于意圖的設(shè)計(jì)。不要因?yàn)闃I(yè)務(wù)代碼重構(gòu)一次,就導(dǎo)致一批case失敗
注意代碼的各種壞味道,可參見(jiàn)《重構(gòu)》第二版
用例可信賴(lài)性
單元測(cè)試,小而且運(yùn)行快,它不是為了發(fā)現(xiàn)本次的bug,更是為了放在流水線上 努力發(fā)現(xiàn)每一次MR是否產(chǎn)生了bug。單測(cè)運(yùn)行失敗,唯一的原因只應(yīng)該是出現(xiàn)bug,而不是因?yàn)橥獠恳蕾?lài)不穩(wěn)定、基于實(shí)現(xiàn)的涉及等,長(zhǎng)期的失敗將失去單元測(cè)試的警示作用,“狼來(lái)了”的故事是慘痛的教訓(xùn)。
非被測(cè)程序缺陷,隨機(jī)失敗的case
永不失敗的case
沒(méi)有assert的case
名不副實(shí)的case
新聞單元測(cè)試的推動(dòng)過(guò)程
我們提到,對(duì)單元測(cè)試的實(shí)踐分為4個(gè)階段,每階段均有目標(biāo)。
第一階段 會(huì)寫(xiě),全員寫(xiě),不要求寫(xiě)好
由上而下的推動(dòng),從總監(jiān)到組長(zhǎng),極力支持,毫無(wú)猶豫,使組員情緒高漲
快速確定單測(cè)框架,熟練使用
結(jié)合開(kāi)發(fā)需求,輸出各場(chǎng)景下 單測(cè)框架的使用方法,包括assert、mock,table-driven等
封裝http2WebContext,方便生成context對(duì)象
多次培訓(xùn),講解單測(cè)理論及框架使用
各團(tuán)隊(duì)(終端、接入層)指定單測(cè)接口人,由他先嘗螃蟹。他是最熟悉框架使用,在前期寫(xiě)最多case的人
在磨合好單測(cè)框架的集成使用后,啟動(dòng)會(huì),部分同學(xué)先試點(diǎn)使用,確保連續(xù)兩個(gè)迭代,這幾個(gè)同學(xué)都有case輸出
每個(gè)迭代總結(jié)數(shù)據(jù)中,加入單測(cè)相關(guān)數(shù)據(jù):組長(zhǎng)和總監(jiān)非常關(guān)注單測(cè)數(shù)據(jù)信息,針對(duì)性鼓勵(lì)提升case數(shù)量和代碼行數(shù)
第二階段 寫(xiě)好,有效,全員寫(xiě)
測(cè)試同學(xué)探索出mock的正確使用方法、用例設(shè)計(jì)的正確思路,分享給團(tuán)隊(duì),經(jīng)過(guò)探討達(dá)成一致
結(jié)對(duì)編程,每迭代結(jié)對(duì)2-3個(gè)開(kāi)發(fā),共同寫(xiě)case,互相提升。
這里的結(jié)對(duì)是靈活的:有的開(kāi)發(fā),只需用半天的時(shí)間給他講框架使用,同他練習(xí),他就可以上手了不需要再擔(dān)心;有的開(kāi)發(fā),會(huì)分給測(cè)試同學(xué)需求,測(cè)試同學(xué)寫(xiě)完case后,開(kāi)發(fā)review學(xué)習(xí),并嘗試寫(xiě)出自己的第一個(gè)case;有的開(kāi)發(fā),一開(kāi)始可能不太接受,以需求不適合單測(cè)為理由,觀察了一段時(shí)間,他發(fā)現(xiàn)其他人都寫(xiě)了,也沒(méi)那么難,對(duì)團(tuán)隊(duì)也有利,他甚至?xí)鲃?dòng)找到測(cè)試同學(xué)教他寫(xiě)case。
測(cè)試同學(xué)對(duì)開(kāi)發(fā)提交的case進(jìn)行review,跟進(jìn)開(kāi)發(fā)修改后重新MR
連續(xù)兩個(gè)迭代,邀請(qǐng)dot老師、喬幫主進(jìn)行case review,效果非常好
對(duì)迭代的單測(cè)數(shù)據(jù)分析,關(guān)注需求覆蓋度、人員覆蓋度,case增量
組長(zhǎng)持續(xù)鼓勵(lì)支持單測(cè)
每迭代的需求增加“單元測(cè)試”字段,由組長(zhǎng)評(píng)估后置位。不帶單測(cè)的MR不予通過(guò),單測(cè)也要被review
第三階段 可測(cè)性提升
測(cè)試和開(kāi)發(fā)共同學(xué)習(xí)《重構(gòu)》第二版,每周有分享會(huì)
某些骨干同學(xué)優(yōu)先重構(gòu)自己的代碼
測(cè)試同學(xué)嚴(yán)格要求,先保證有單測(cè),然后小步重構(gòu),每一步均有單測(cè)保障
通過(guò)流水線的codecc掃描,圈復(fù)雜度和函數(shù)長(zhǎng)度必須達(dá)標(biāo),不可人工干預(yù)其通過(guò)
第四階段 TDD
先不保證開(kāi)發(fā)同學(xué)做到TDD,門(mén)檻還是挺高的,而且需要在線下熟練之后再運(yùn)用到業(yè)務(wù)開(kāi)發(fā)中
逐步推動(dòng)開(kāi)發(fā)將業(yè)務(wù)代碼和測(cè)試代碼同步編寫(xiě),而不是完成業(yè)務(wù)代碼后再補(bǔ)case
測(cè)試同學(xué)練成TDD
流水線
單測(cè)要放在流水線上跑,客戶(hù)端和后臺(tái)都配好了流水線,保證每次push和MR都運(yùn)行一次,發(fā)報(bào)告。
對(duì)于go的單測(cè),新聞接入層各模塊是通過(guò)MakeFile來(lái)編譯,因?yàn)橐獙?dǎo)入一些環(huán)境變量,所以我將go test集成在MakeFile中,執(zhí)行make test即可運(yùn)行該模塊下所有的測(cè)試用例。
GO = go CGO_LDFLAGS = xxxCGO_LDFLAGS += xxxCGO_LDFLAGS += xxxCGO_LDFLAGS+=xxx TARGET=aaa export CGO_LDFLAGS all:$(TARGET) $(TARGET): main.go $(GO) build -o $@ $^test: CFLAGS=-g export CFLAGS $(GO) test $(M) -v -gcflags=all=-l -coverpkg=./... -coverprofile=test.out ./...clean: rm -f $(TARGET)
注:上述做法,只能生成被測(cè)試的代碼文件的覆蓋率,無(wú)法拿到未被測(cè)試覆蓋率情況。可以在根目錄建一個(gè)空的測(cè)試文件,就能解決這個(gè)問(wèn)題,拿到全量代碼覆蓋率。
//main_test.gopackage main import ( "fmt" "testing") func TestNothing(t *testing.T) { fmt.Println("ok")}
流水線加上流程
# cd ${WORKSPACE} 可進(jìn)入當(dāng)前工作空間目錄export GOPATH=${WORKSPACE}/xxxpwd echo "====================work space"echo ${WORKSPACE}cd ${GOPATH}/srcfor file in `ls`:do if [ -d $file ] then if [[ "$file" == "a" ]] || [[ "$file" == "b" ]] || [[ "$file" == "c" ]] || [[ "$file" == "d" ]] then echo $file echo ${GOPATH}"/src/"$file cp -r ${GOPATH}/src/tools/qatesting/main_test.go ${GOPATH}/src/$file"/." cd ${GOPATH}/src/$file make test cd .. fi fidone
-
函數(shù)
+關(guān)注
關(guān)注
3文章
4374瀏覽量
64414 -
單元測(cè)試
+關(guān)注
關(guān)注
0文章
50瀏覽量
3292
原文標(biāo)題:從頭到腳說(shuō)單測(cè)——談?dòng)行У膯卧獪y(cè)試
文章出處:【微信號(hào):Tencent_TEG,微信公眾號(hào):騰訊技術(shù)工程官方號(hào)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄

新能源車(chē)軟件單元測(cè)試深度解析:自動(dòng)駕駛系統(tǒng)視角
新能源車(chē)背后的隱形守護(hù)者:軟件單元測(cè)試的生死較量?
單元測(cè)試:構(gòu)建數(shù)字世界的質(zhì)量基石
單元測(cè)試在嵌入式軟件中的關(guān)鍵作用及winAMS工具的卓越貢獻(xiàn)
嵌入式軟件單元測(cè)試的必要性、核心方法及工具深度解析
嵌入式系統(tǒng)開(kāi)發(fā)中的測(cè)試方法 嵌入式系統(tǒng)開(kāi)發(fā)與AI結(jié)合應(yīng)用
開(kāi)發(fā)者必讀!CircleCI?組件測(cè)試與單元測(cè)試全解析
汽車(chē)軟件單元測(cè)試的重要性
嚴(yán)格的單元測(cè)試造就完美的軟件

TESSY單元測(cè)試工具詳解與操作演示:ISO 26262合規(guī)性、自定義測(cè)試用例、詳細(xì)測(cè)試報(bào)告等
嵌入軟件單元/集成測(cè)試工具專(zhuān)業(yè)分析
Linux內(nèi)核測(cè)試技術(shù)

鴻蒙語(yǔ)言基礎(chǔ)類(lèi)庫(kù):ohos.application.testRunner TestRunner 測(cè)試
單元測(cè)試、集成測(cè)試自動(dòng)化工具

評(píng)論