公交车上荫蒂添的好舒服的电影-公用玩物(np双xing总受)-公用小荡货芊芊-公与妇仑乱hd-攻把受做哭边走边肉楼梯play-古装一级淫片a免费播放口

LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開(kāi)發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

單元測(cè)試從入門(mén)到精通

freeflydom
2025年3月10日 16:5 本文熱度 841

1 前言

這篇文章源于工作中的一個(gè)項(xiàng)目,2021年,我負(fù)責(zé)匯川技術(shù)工業(yè)機(jī)器人應(yīng)用軟件的基礎(chǔ)架構(gòu)重構(gòu),當(dāng)時(shí)單元測(cè)試是重構(gòu)工作的核心環(huán)節(jié)之一,從無(wú)法進(jìn)行單元測(cè)試到最終60%以上的行覆蓋率,過(guò)程中自己也有非常多的收獲,于是將其整理成文,希望對(duì)計(jì)劃開(kāi)展和正在開(kāi)展單元測(cè)試的同學(xué)有所幫助。

2 什么是單元測(cè)試

 

單元測(cè)試(Unit Testing),是指對(duì)軟件中的邏輯單元或組件進(jìn)行檢查和驗(yàn)證,以確保其按預(yù)期執(zhí)行。通常單元測(cè)試是軟件開(kāi)發(fā)過(guò)程中進(jìn)行的最低級(jí)別測(cè)試活動(dòng),通過(guò)單元測(cè)試可發(fā)現(xiàn)和修復(fù)軟件開(kāi)發(fā)早期的BUG和缺陷。

單元(Unit),是一個(gè)應(yīng)用程序中最小的可測(cè)試部分,在面向過(guò)程開(kāi)發(fā)中,單元通常為函數(shù)(Function),在面向?qū)ο箝_(kāi)發(fā)中,單元通常為類中的方法(Method)。

3 為什么要進(jìn)行單元測(cè)試

 

3.1 降低代碼缺陷

 

單元測(cè)試的首要目標(biāo)是降低代碼缺陷,如上圖所示,當(dāng)代碼缺陷越早被發(fā)現(xiàn),它的修復(fù)成本就越低,這種把測(cè)試盡量提前進(jìn)行的思想就叫做測(cè)試左移。

3.2 推動(dòng)架構(gòu)優(yōu)化

 

單元測(cè)試與軟件架構(gòu)有著非常緊密的聯(lián)系,通常越是架構(gòu)設(shè)計(jì)優(yōu)秀的項(xiàng)目(比如符合SOLID規(guī)則),越容易實(shí)施單元測(cè)試,反之越是架構(gòu)糟糕的項(xiàng)目,越難以實(shí)施單元測(cè)試。并且單元測(cè)試以及其嚴(yán)格的方式要求軟件架構(gòu)設(shè)計(jì),如果架構(gòu)設(shè)計(jì)存在問(wèn)題,單元測(cè)試就根本無(wú)法開(kāi)展。

假如你發(fā)現(xiàn)在自己的項(xiàng)目中實(shí)施單元測(cè)試舉步為艱,那么首先應(yīng)該停下來(lái)觀察和思考一下,項(xiàng)目架構(gòu)設(shè)計(jì)的是否合理?比如一些項(xiàng)目中UI與業(yè)務(wù)邏輯耦合,單元測(cè)試無(wú)法命中核心業(yè)務(wù)邏輯,可思考一下項(xiàng)目是否需要分層設(shè)計(jì)?UI與業(yè)務(wù)邏輯是否需要分離?比如項(xiàng)目中當(dāng)碰到物理設(shè)備的依賴,單元測(cè)試就被阻斷,可思考一下是否永遠(yuǎn)只使用這一臺(tái)設(shè)備?后續(xù)有沒(méi)有可能換成其它設(shè)備?是否需要考慮擴(kuò)展性?再比如發(fā)現(xiàn)被測(cè)對(duì)象就是鐵板一塊,根本不能改變其協(xié)作對(duì)象的行為和數(shù)據(jù),那么就應(yīng)思考一下,對(duì)象之間是否存在強(qiáng)耦合?是否可以通過(guò)依賴注入降低和消除對(duì)象之間的耦合?

大多數(shù)情況我們?cè)陧?xiàng)目中實(shí)施單元測(cè)試的目的是為了保障代碼質(zhì)量,但我認(rèn)為單元測(cè)試對(duì)軟件架構(gòu)優(yōu)化的驅(qū)動(dòng)實(shí)際更為重要。

3.3 守護(hù)代碼迭代質(zhì)量

 

在當(dāng)下的商業(yè)環(huán)境中,大魚(yú)吃小魚(yú),快魚(yú)吃慢魚(yú),對(duì)軟件開(kāi)發(fā)的效率要求越來(lái)越高,傳統(tǒng)的瀑布開(kāi)發(fā)模式越來(lái)越少,敏捷開(kāi)發(fā)模式越來(lái)越普及。因此軟件版本快速迭代,快速測(cè)試,快速發(fā)布,小步快跑在大多數(shù)項(xiàng)目中成為常態(tài)。

而單元測(cè)試在代碼快速迭代過(guò)程中發(fā)揮著守護(hù)代碼質(zhì)量的至關(guān)重要作用。當(dāng)單元測(cè)試覆蓋了一個(gè)模塊中的業(yè)務(wù)邏輯,該業(yè)務(wù)邏輯在迭代變更過(guò)程中出現(xiàn)任何問(wèn)題,會(huì)第一時(shí)間自動(dòng)被單元測(cè)試捕獲,因此單元測(cè)試對(duì)發(fā)生變更的代碼正確性提供了保障,同時(shí)開(kāi)發(fā)人員在這樣的保障下可以大膽的對(duì)代碼進(jìn)行重構(gòu),對(duì)業(yè)務(wù)邏輯進(jìn)行增減變更調(diào)整。大名鼎鼎的TDD(測(cè)試驅(qū)動(dòng)開(kāi)發(fā))就是基于這個(gè)原理。

4 如何進(jìn)行單元測(cè)試

 

4.1 使用AAA規(guī)則編寫(xiě)測(cè)試用例

我們用一個(gè)簡(jiǎn)單的示例來(lái)演示單元測(cè)試的編碼過(guò)程,如下代碼所示,是一個(gè)非常簡(jiǎn)單的方法,它根據(jù)不同的距離,推薦不同的交通工具:

class UnitTestDemo
{
    public:
    // 一個(gè)被測(cè)方法,根據(jù)距離推薦交通工具
    // distance參數(shù):距離(單位為千米)
    string StransportForDistance(float distance)
    {
        // 100公里內(nèi)推薦的士  
        if (distance <= 100)  
            return "的士";  
        // 1000公里內(nèi)推薦高鐵  
        if (distance <= 1000)  
            return "高鐵";  
        // 大于1000公里推薦飛機(jī)  
        return "飛機(jī)";  
    }  
}  

然后我們?yōu)檫@個(gè)方法編寫(xiě)單元測(cè)試用例,它同樣非常的簡(jiǎn)單:設(shè)定條件,調(diào)用被測(cè)方法,斷言返回結(jié)果:

// 一個(gè)單元測(cè)試用例
TEST(UnitTestDemo, TransportForDistance_Texi)
{
    // 設(shè)置初始條件(Arrange)
    UnitTestDemo unitTestDemo;
    float distance = 30;
    // 執(zhí)行業(yè)務(wù)邏輯(Act)
    string s = unitTestDemo.TransportForDistance(distance);
    // 斷言測(cè)試結(jié)果(Assert)
    EXPECT_EQ(s, "的士");
} 

至此,單元測(cè)試的編碼就完成了。是的,單元測(cè)試的編碼已全部完成了,花30秒看懂這個(gè)示例,你就掌握了單元測(cè)試的核心方法。為了方便記憶,有人將它總結(jié)成了AAA規(guī)則:

Arrange

設(shè)置條件

Act

執(zhí)行邏輯

Assert

斷言結(jié)果

4.2 讓每個(gè)測(cè)試用例符合AIR特性

AAA規(guī)則告訴我們?nèi)绾尉帉?xiě)單元測(cè)試用例,但要編寫(xiě)一個(gè)合格的單元測(cè)試用例,就需要了解單元測(cè)試用例的基本特性,這些特性就像空氣(AIR)一樣重要,任何時(shí)候我們也不能離開(kāi):

Automatic

自動(dòng)化:?jiǎn)卧獪y(cè)試應(yīng)自動(dòng)執(zhí)行,而無(wú)需任何交互,測(cè)試用例通常被定期執(zhí)行。

Independent

獨(dú)立性:每個(gè)單元測(cè)試用例都是獨(dú)立的個(gè)體,不允許測(cè)試用例之間存在依賴關(guān)系,也不允許要求測(cè)試用例被執(zhí)行的先后順序。

Repeatable

可重復(fù):?jiǎn)卧獪y(cè)試用例在被重復(fù)執(zhí)行時(shí)應(yīng)穩(wěn)定的返回相同的結(jié)果,不能受外部環(huán)境的影響。

4.3 在需要的時(shí)候使用測(cè)試替身

 

什么是測(cè)試替身

比如業(yè)務(wù)中我們的代碼與硬件設(shè)備連接,需要依賴硬件的不同狀態(tài)來(lái)執(zhí)行不同的邏輯。單元測(cè)試的特性是隨時(shí)可重復(fù)執(zhí)行,對(duì)硬件的依賴會(huì)阻塞單元測(cè)試執(zhí)行,因此在單元測(cè)試用例中需要用一個(gè)“替身”替換掉硬件的狀態(tài),這個(gè)“替身”就叫做測(cè)試替身。

測(cè)試替身的應(yīng)用場(chǎng)景

1、真實(shí)對(duì)象具有不可確定的行為,或產(chǎn)生不可預(yù)測(cè)的結(jié)果。

2、真實(shí)對(duì)象很難被創(chuàng)建或創(chuàng)建成本過(guò)大,比如第三方系統(tǒng)、與硬件設(shè)備關(guān)聯(lián)的模塊。

3、真實(shí)對(duì)象的某些行為很難觸發(fā),比如異常的觸發(fā)。

4、真實(shí)對(duì)象令測(cè)試用例的執(zhí)行速度很慢。

5、真實(shí)對(duì)象有含有人機(jī)交互界面。

4.4 了解單元測(cè)試覆蓋方式

 

語(yǔ)句覆蓋

語(yǔ)句覆蓋又稱為行覆蓋,是單元測(cè)試中最簡(jiǎn)單也是最常見(jiàn)的覆蓋率統(tǒng)計(jì)方式。被測(cè)函數(shù)中,只要被單元測(cè)試用例執(zhí)行到的行,即認(rèn)為該行被覆蓋到。比如一個(gè)100行的函數(shù),其中有60行被單元測(cè)試用例執(zhí)行到,那么語(yǔ)句覆蓋率為60%。

分支覆蓋

分支覆蓋又稱為判定覆蓋,它關(guān)注的是被測(cè)函數(shù)中產(chǎn)生分支的if判定結(jié)果,只要每個(gè)if語(yǔ)句判定為真和判定為假的分支都被執(zhí)行到,即達(dá)成了分支覆蓋。注意分支覆蓋并不考慮多個(gè)分支間的組合關(guān)系。

條件覆蓋

條件覆蓋關(guān)注的是判定語(yǔ)句中的每個(gè)表達(dá)式是否被執(zhí)行。比如判定語(yǔ)句 if (a() || b()) ,當(dāng)a()返回為真時(shí)就不再執(zhí)行b()了,此時(shí)就未達(dá)成條件覆蓋;要達(dá)成條件覆蓋,就需要使a()返回假。

路徑覆蓋

路徑覆蓋是單元測(cè)試中覆蓋最全的一種方式,它要求覆蓋被測(cè)試方法中所有邏輯分支路徑的組合。

總結(jié)

語(yǔ)句覆蓋在單元測(cè)試覆蓋率統(tǒng)計(jì)中最為常見(jiàn),基本是一個(gè)必選項(xiàng),分支覆蓋與條件覆蓋可作為進(jìn)階選擇,路徑覆蓋最為完善,但是在復(fù)雜的業(yè)務(wù)場(chǎng)景中,會(huì)導(dǎo)致單元測(cè)試代碼指數(shù)級(jí)增長(zhǎng)。建議根據(jù)實(shí)際情況靈活組合搭配。

4.5 單元測(cè)試過(guò)程中的一些常見(jiàn)疑問(wèn)

 

由誰(shuí)來(lái)編寫(xiě)單元測(cè)試用例?

應(yīng)該由開(kāi)發(fā)人員編寫(xiě)自己開(kāi)發(fā)的功能對(duì)應(yīng)的單元測(cè)試用例。有些項(xiàng)目中會(huì)安排專人來(lái)為其它人開(kāi)發(fā)的功能編寫(xiě)單元測(cè)試用例,這樣做效率很低,因?yàn)閱卧獪y(cè)試用例的編寫(xiě)人員需要花費(fèi)時(shí)間了解和學(xué)習(xí)代碼邏輯。

什么時(shí)間節(jié)點(diǎn)寫(xiě)單元測(cè)試用例?

通常應(yīng)該在功能開(kāi)發(fā)完成后即編寫(xiě)與之對(duì)應(yīng)的單元測(cè)試用例,即使有延遲也不要延遲太長(zhǎng)時(shí)間,時(shí)間過(guò)長(zhǎng)會(huì)導(dǎo)致編寫(xiě)單元測(cè)試用例時(shí)需要重新回顧代碼邏輯所帶來(lái)的額外時(shí)間成本。

單元測(cè)試是白盒還是黑盒測(cè)試?

絕大部分的單元測(cè)試是白盒測(cè)試,會(huì)根據(jù)函數(shù)中的邏輯設(shè)計(jì)編寫(xiě)測(cè)試用例,以達(dá)到覆蓋率目標(biāo)。但單元測(cè)試也可以是黑盒測(cè)試,比如一些API接口只關(guān)注輸入與輸出而不關(guān)注內(nèi)部的邏輯實(shí)現(xiàn)。

5 可測(cè)試性設(shè)計(jì)

 

5.1 分層設(shè)計(jì)

 

將一件復(fù)雜的事情進(jìn)行分解,是提升效率的基本手段,這在日常生活中非常常見(jiàn)。比如汽車的生產(chǎn)過(guò)程離散在多個(gè)零部件生產(chǎn)線,最后完成組裝。軟件中的分層設(shè)計(jì),也是最常見(jiàn)的一種架構(gòu)模式,在流行的開(kāi)發(fā)框架中隨處可見(jiàn)。分層設(shè)計(jì)可以幫助單元測(cè)試準(zhǔn)確的命中目標(biāo),比如通常情況下我們并不需要對(duì)UI而只希望對(duì)核心的業(yè)務(wù)邏輯進(jìn)行單元測(cè)試,如果沒(méi)有分層,UI與業(yè)務(wù)邏輯耦合,就會(huì)使單元測(cè)試無(wú)法準(zhǔn)確命中目標(biāo)甚至寸步難行。

5.2 抽象設(shè)計(jì)

 

抽象是增強(qiáng)軟件擴(kuò)展性的一把利劍,主板廠商很早就把抽象應(yīng)用自如了。比如主板上的USB接口,并不針對(duì)某一種具體的設(shè)備,而只定義了USB標(biāo)準(zhǔn):接口尺寸、電流、電壓、數(shù)據(jù)傳輸協(xié)議等,然后依據(jù)這個(gè)標(biāo)準(zhǔn)生產(chǎn)主板。USB標(biāo)準(zhǔn)即抽象,主板廠商通過(guò)抽象獲得了對(duì)無(wú)限種類USB設(shè)備的擴(kuò)展支持。

因?yàn)橛辛薝SB標(biāo)準(zhǔn),所以很容易就可以設(shè)計(jì)生產(chǎn)一個(gè)USB測(cè)試工裝,這個(gè)工裝就類似于單元測(cè)試中的測(cè)試替身,主板廠商在測(cè)試時(shí),并不需要外接一個(gè)用戶經(jīng)常使用的U盤(pán)或USB鍵盤(pán),而只需要外接一個(gè)USB測(cè)試工裝即可完成測(cè)試,并且這個(gè)測(cè)試工裝可以在符合USB基本標(biāo)準(zhǔn)的前提下按測(cè)試需求設(shè)計(jì)生產(chǎn),比如只需要按數(shù)據(jù)傳輸標(biāo)準(zhǔn)接收數(shù)據(jù)即可而并不需要真正的存儲(chǔ)數(shù)據(jù)。

5.3 依賴注入

 

當(dāng)發(fā)生火警時(shí),消防通道的暢通保障了救援。在單元測(cè)試中,依賴注入保障了代碼的可測(cè)試。由此可見(jiàn),依賴注入在可測(cè)試性編碼中的重要性。

// 上帝視角
上帝造人()
{
    自己捏腦袋();
    自己捏胳膊();
    自己捏腿();
    ……
}
// 依賴注入
上帝造人( 腦袋, 胳膊, 腿 ……)
{
    組裝腦袋();
    組裝胳膊();
    組裝腿();
    ……
} 

如果所有職業(yè)按成就感進(jìn)行排名的話,我想軟件開(kāi)發(fā)一定是名列前茅的,因?yàn)榇蠖鄶?shù)時(shí)候軟件開(kāi)發(fā)人員扮演的就是“上帝角色”,他們可以隨時(shí)new一切需要的對(duì)象。但在依賴注入模式下,上帝需要從“自己創(chuàng)造”轉(zhuǎn)變?yōu)椤傲?xí)慣組裝”。

6 可測(cè)試性編碼

Google的研發(fā)工程師寫(xiě)了一篇關(guān)于軟件可測(cè)試性的文章《Guide: Writing Testable Code》,覺(jué)得里面的代碼示例比較具有代表性,摘錄并整理簡(jiǎn)化了代碼(可不關(guān)注語(yǔ)法細(xì)節(jié),當(dāng)作偽代碼來(lái)看)如下:

6.1 注入?yún)f(xié)作對(duì)象

難以測(cè)試的代碼示例:

// 被測(cè)對(duì)象
public class House
{
    private Bedroom bedroom;
    House() 
    { 
        // 在類的構(gòu)造函數(shù)中構(gòu)造協(xié)作對(duì)象,可測(cè)試性差。
        bedroom = new Bedroom(); 
    }
    // ...
}
// 測(cè)試用例
public void TestThisIsReallyHard()
{
    House house = new House();
    // 無(wú)法控制Bedroom對(duì)象,難以測(cè)試
    // ...
} 

易于測(cè)試的代碼示例:

// 被測(cè)對(duì)象
public class House
{
    private Bedroom bedroom;
    // 注入?yún)f(xié)作對(duì)象,可測(cè)試性好。
    House(Bedroom b) 
    { 
        bedroom = b;
    }
    // ...
}
// 測(cè)試用例
public void TestThisIsEasyAndFlexible()
{
    // Bedroom對(duì)象在掌控之中,易于測(cè)試
    Bedroom bedroom = new Bedroom();
    House house = new House(bedroom);
    // ...
} 

6.2 不要依賴靜態(tài)方法

難以測(cè)試的代碼示例:

// 被測(cè)對(duì)象
public class TrainSchedules 
{ 
    Schedule FindNextTrain() 
    { 
        // 與靜態(tài)方法強(qiáng)耦合,難以測(cè)試
        if (TrackStatusChecker.IsClosed(track)) 
        { 
            // ...
        } 
        // ... return a Schedule
    } 
}
// 測(cè)試用例
public void TestFindNextTrainNoClosings()
{
    // 靜態(tài)方法出現(xiàn)長(zhǎng)耗時(shí),阻塞單元測(cè)試
    AssertNotNull(schedules.FindNextTrain());
}

易于測(cè)試的代碼示例:

// 將靜態(tài)方法包裝在一個(gè)注入類中,并將其抽象(設(shè)計(jì)實(shí)現(xiàn)IStatusChecker接口)
public class TrackStatusCheckerWrapper : IStatusChecker
{ 
    public bool IsClosed(Track track) 
    { 
        return TrackStatusChecker.IsClosed(track); 
    } 
} 
// 被測(cè)對(duì)象
public class TrainSchedules 
{ 
    private StatusChecker wrappedLibrary; 
    // 1、通過(guò)依賴注入解除與靜態(tài)方法之間的強(qiáng)耦合
    // 2、支持通過(guò)抽象使用測(cè)試替身,消除長(zhǎng)耗時(shí)
    public TrainSchedules(IStatusChecker wrappedLibrary) 
    { 
        this.wrappedLibrary = wrappedLibrary; 
    } 
    public Schedule FindNextTrain() 
    { 
        if (wrappedLibrary.IsClosed(track)) 
        { 
            // ... 
        } 
        // ... return a Schedule 
    } 
}
// 測(cè)試用例
public void TestFindNextTrainNoClosings() 
{
    // 支持通過(guò)抽象替換掉靜態(tài)方法中的耗時(shí)邏輯
    IStatusChecker localWrapper = new StubStatusCheckerWrapper();
    TrainSchedules schedules = new TrainSchedules(localWrapper);
    AssertNotNull(schedules.FindNextTrain()); 
} 

6.3 不要依賴全局變量

難以測(cè)試的代碼示例:

// 被測(cè)對(duì)象
public class NetworkLoadCalculator
{
    public int CalculateTotalLoad()
    {
        // 依賴全局變量,難以測(cè)試
        string algorithm = ConfigFlags.FLAG_loadAlgorithm.Get();
        // ...
    }
}
// 測(cè)試用例
public void TestMaximumAlgorithmReturnsHighestLoad() 
{ 
    // 缺陷1:一旦忘了復(fù)原全局變量就會(huì)導(dǎo)致后續(xù)其它測(cè)試用例執(zhí)行失敗
    // 缺陷2:全局變量導(dǎo)致測(cè)試用例無(wú)法并行執(zhí)行
    // 設(shè)置全局變量
    ConfigFlags.FLAG_loadAlgorithm.SetForTest("maximum"); 
    NetworkLoadCalculator calc = new NetworkLoadCalculator(); 
    calc.SetLoadSources(10, 5, 0); 
    AssertEquals(10, calc.CalculateTotalLoad()); 
    // 復(fù)原全局變量
    ConfigFlags.FLAG_loadAlgorithm.ResetForTest(); 
} 

易于測(cè)試的代碼示例:

// 被測(cè)對(duì)象
public class NetworkLoadCalculator 
{ 
    private string loadAlgorithm; 
    // 使用依賴注入,可測(cè)試性好
    NetworkLoadCalculator(string loadAlgorithm) 
    { 
        this.loadAlgorithm = loadAlgorithm; 
    } 
    // ... 
}
// 測(cè)試用例
public void TestMaximumAlgorithmReturnsHighestLoad() 
{
    // 不再依賴全局變量,解除了測(cè)試用例之間相互影響的風(fēng)險(xiǎn)
    NetworkLoadCalculator calc = new NetworkLoadCalculator("maximum"); 
    calc.SetLoadSources(10, 5, 0); 
    AssertEquals(10, calc.CalculateTotalLoad()); 
} 

6.4 不要為了測(cè)試而測(cè)試

難以測(cè)試的代碼示例:

// 協(xié)作對(duì)象
public class VideoPlaylistIndex
{
    private VideoRepository repo;
    // 單元測(cè)試專屬構(gòu)造函數(shù),業(yè)務(wù)中不使用
    public VisibleForTesting VideoPlaylistIndex (VideoRepository repo)
    {
        this.repo = repo;
    }
    // 業(yè)務(wù)中使用的構(gòu)造函數(shù)
    public VideoPlaylistIndex()
    {
        // 執(zhí)行緩慢的邏輯
        this.repo = new FullLibraryIndex();
    }
}
// 被測(cè)對(duì)象
public class PlaylistGenerator
{
    private VideoPlaylistIndex index = new VideoPlaylistIndex();
    public Playlist buildPlaylist(Query q)
    {
        return index.search(q);
    }
}
// 測(cè)試用例
public void TestBadDesignHasNoSeams()
{
    // 雖然VideoPlaylistIndex容易進(jìn)行測(cè)試,但PlaylistGenerator卻難以測(cè)試,執(zhí)行緩慢的構(gòu)造函數(shù)無(wú)法被替換
    PlaylistGenerator generator = new PlaylistGenerator();
} 

易于測(cè)試的代碼示例:

// 協(xié)作對(duì)象
public class VideoPlaylistIndex
{
    private VideoRepository repo;
    // 業(yè)務(wù)與單元測(cè)試共用構(gòu)造函數(shù)
    public VisibleForTesting VideoPlaylistIndex (VideoRepository repo)
    {
        this.repo = repo;
    }
}
// 被測(cè)對(duì)象
public class PlaylistGenerator
{
    private VideoPlaylistIndex index;
    // 使用依賴注入
    public PlaylistGenerator(VideoPlaylistIndex index)
    {
        this.index = index;
    }
    public Playlist buildPlaylist(Query q)
    {
        return index.search(q);
    }
}
// 測(cè)試用例
public void TestFlexibleDesignWithDI()
{
    // 通過(guò)依賴注入替換掉可能耗時(shí)的操作
    VideoPlaylistIndex fakeIndex = new InMemoryVideoPlaylistIndex();
    PlaylistGenerator generator = new PlaylistGenerator(fakeIndex);
} 

7 使用測(cè)試框架

 

7.1 GTest簡(jiǎn)介

測(cè)試框架為我們提供了測(cè)試用例管理、斷言、參數(shù)化、用例執(zhí)行等系列通用功能,使我們可以專注于測(cè)試用例本身業(yè)務(wù)邏輯的處理。在C/C++編程中,GTest當(dāng)前最流行的單元測(cè)試框架,它由Google公司發(fā)布,支持跨平臺(tái)(Linux、Windows、MacOS),GTest官方倉(cāng)庫(kù)地址為:https://github.com/google/googletest

7.2 使用GTest編寫(xiě)單元測(cè)試用例

// 一個(gè)簡(jiǎn)單的單元測(cè)試用例示例
TEST(Test_Suite_Name, Test_Case_Name)
{
    // 設(shè)置初始條件
    // ...
    // 調(diào)用被測(cè)試方法
    // ..
    // 斷言測(cè)試結(jié)果
    EXPECT_EQ(varString, "Assert Result");
} 

GTest框架會(huì)自動(dòng)執(zhí)行所有單元測(cè)試用例(由TEST、TEST_F等宏定義),一個(gè)單元測(cè)試用例類似于一個(gè)函數(shù),其中第一個(gè)參數(shù)為測(cè)試套件名稱,測(cè)試套件就是一系列單元測(cè)試用例的集合,第二個(gè)參數(shù)為單元測(cè)試用例名稱,如上代碼所示。

// 自定義一個(gè)測(cè)試套件
class Test_Suite : public ::testing::Test
{
protected:
    // 全局初始化(所有測(cè)試用例執(zhí)行前)
    static void SetUpTestCase()
    {}
    // 全局清理(所有測(cè)試用例執(zhí)行后)
    static void TearDownTestCase()
    {}
    // 測(cè)試用例初始化(單個(gè)測(cè)試用例執(zhí)行前)
    void SetUp() override
    {}
    // 測(cè)試用例清理(單個(gè)測(cè)試用例執(zhí)行后)
    void TearDown() override
    {}
    // 其它公共資源
    // ...
}
// 使用自定義測(cè)試套件的單元測(cè)試用例
TEST_F(Test_Suite, Test_Case_Name)
{
    // 設(shè)置初始條件
    // ...
    // 調(diào)用被測(cè)方法
    // ..
    // 斷言測(cè)試結(jié)果
    EXPECT_TRUE(varBool);
} 

在實(shí)際項(xiàng)目中,通常相同類型的多個(gè)測(cè)試用例需要相同的初始化和清理過(guò)程,或需要共用一些資源。此時(shí)就可以使用自定義測(cè)試套件方式,如上代碼所示。

7.3 單元測(cè)試覆蓋率統(tǒng)計(jì)

單元測(cè)試覆蓋率通常指的是行覆蓋率,其計(jì)算規(guī)則為:分母為被測(cè)項(xiàng)目有效代碼(排除空白、注釋等無(wú)效行)的總行數(shù),分子為被單元測(cè)試用例執(zhí)行到的行數(shù),由此計(jì)算的比例為單元測(cè)試行覆蓋率。華為大多軟件項(xiàng)目對(duì)外宣稱的單元測(cè)試行覆蓋率為70%,根據(jù)我的經(jīng)驗(yàn),這是一個(gè)相當(dāng)高的比例了。

有很多統(tǒng)計(jì)單元測(cè)試覆蓋率的工具,比如針對(duì)C++的 OpenCppCoverage ,安裝后通過(guò)一條命令即可生成HTML可視化的單元測(cè)試覆蓋率統(tǒng)計(jì)報(bào)表:

OpenCppCoverage.exe --source 待分析的源代碼目錄 -- 單元測(cè)試項(xiàng)目生成的.exe

8 單元測(cè)試的成敗關(guān)鍵

 

8.1 時(shí)間與成本預(yù)算

決定在項(xiàng)目中實(shí)施單元測(cè)試前,需要與項(xiàng)目經(jīng)理充分溝通項(xiàng)目時(shí)間周期與成本,因?yàn)閱卧獪y(cè)試需要增加開(kāi)發(fā)工程師在編碼階段的時(shí)間投入,這個(gè)比例大致在0.5~1.0之間。即假如某個(gè)功能的編碼時(shí)間是10天,那么需要增加大約5-10天來(lái)完成單元測(cè)試。同時(shí)單元測(cè)試并非一勞永逸,后續(xù)當(dāng)被測(cè)試的業(yè)務(wù)代碼發(fā)生變更,與之對(duì)應(yīng)的單元測(cè)試用例也需要同步變更。因此獲得相應(yīng)的項(xiàng)目資源預(yù)算對(duì)單元測(cè)試的成敗至關(guān)重要,如果沒(méi)有給到開(kāi)發(fā)人員相對(duì)充裕的時(shí)間,但又要求他們達(dá)成單元測(cè)試指標(biāo),就會(huì)導(dǎo)致開(kāi)發(fā)人員認(rèn)為單元測(cè)試擠占了功能開(kāi)發(fā)時(shí)間,從而排斥單元測(cè)試。

8.2 在軟件架構(gòu)設(shè)計(jì)階段整體考慮可測(cè)試性(架構(gòu)師)

 

可測(cè)試性架構(gòu)設(shè)計(jì)是達(dá)成單元測(cè)試在技術(shù)層面最重要的環(huán)節(jié),好比房屋裝修,如果軟裝都完成了,冰箱彩電空調(diào)擺放就位,才發(fā)現(xiàn)忘了走電源線,那么補(bǔ)救成本就非常高了。

8.3 在編碼階段具備可測(cè)試性意識(shí)(開(kāi)發(fā)工程師)

除了架構(gòu)設(shè)計(jì)提前考慮對(duì)單元測(cè)試的支持,軟件編碼亦是如此,開(kāi)發(fā)人員在編寫(xiě)代碼前應(yīng)提前了解單元測(cè)試,以不至于編寫(xiě)出來(lái)的代碼不能或難以進(jìn)行單元測(cè)試。比如全局變量滿天飛導(dǎo)致測(cè)試用例之間相互影響,類中的協(xié)作對(duì)象完全不使用依賴注入導(dǎo)致測(cè)試用例無(wú)從下手,等等。

9 后記

本文介紹了單元測(cè)試的基本概念,以及結(jié)合實(shí)際項(xiàng)目,分享了單元測(cè)試實(shí)施要點(diǎn)。是對(duì)自己項(xiàng)目過(guò)程的總結(jié),也希望對(duì)有需要的同學(xué)有所幫助。

最后做一點(diǎn)補(bǔ)充,實(shí)施單元測(cè)試大致分為兩類,我稱之為主動(dòng)單元測(cè)試和被動(dòng)單元測(cè)試,主動(dòng)單元測(cè)試,是以提升代碼質(zhì)量和軟件架構(gòu)為目的,由內(nèi)部主動(dòng)發(fā)起,實(shí)施過(guò)程中會(huì)同步優(yōu)化軟件架構(gòu)、提升代碼可測(cè)試性。而被動(dòng)單元測(cè)試由外部驅(qū)使,比如來(lái)自客戶或市場(chǎng)的外部要求,它以覆蓋率為唯一目標(biāo),通常會(huì)借助一些商業(yè)工具(比如Tessy),自動(dòng)生成單元測(cè)試用例與完成打樁,它不需要修改源程序代碼,當(dāng)然也不會(huì)提升軟件的架構(gòu)質(zhì)量。本文所描述的,以及我個(gè)人比較推崇的為主動(dòng)單元測(cè)試。

<全文完>

?轉(zhuǎn)自https://www.cnblogs.com/wubayue/p/18760269


該文章在 2025/3/10 16:06:07 編輯過(guò)
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開(kāi)發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫(kù)管理,保質(zhì)期管理,貨位管理,庫(kù)位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 国产成人免费午夜在线观看 | 国产精品一区二区熟女不卡 | 国产午夜影 | 国产精品白浆无码浪潮av | 变态另类欧美大码日韩 | 国产精品国产三级国v麻豆 国产精品国产三级国产 | av无码久久久久不卡网站下载 | 91丝袜在线播放 | 精品国产v无码大片在线观看 | 国产美女合集 | 国产精品理论在线观看 | 国产一区二区三区精品91 | 国产女人喷浆 | 动漫av纯肉无码av电影网 | 国产精品无码亚洲精品2025 | 国产a级毛片久久久精品毛片 | 国偷自产在线精品 | 国产高清国内精品福利色噜噜 | 国产一卡二卡三卡 | 国产极品人妖在线观看 | 精品无人国产偷自产在线 | 国产一区日本二区欧美三区 | 国产国语精品对白无码视频 | 国外av无码精品国产精品 | 国产99久久精品 | 成人三级视频在线观看不卡 | 黑人巨大欧美精品一区二区o | 国产成人午夜福利电影在线播放 | 国产真实高中生在线在线观看 | av无码网址 | 国产美女冒白浆免 | 97色伦图片9| 国产呦精品一区二区三区下载 | 精品国产成人系列 | 国产成人精品一区二区三区不卡 | av片无码久久尤物 | 国产在线观看欧 | 东京热中文字幕a∨无码 | 国产精品毛片一区二区三区在 | 国产精品岛国久久久久 | 国产原创av一区二区三区 |