通義靈碼實踐教程——單元測試

通义灵码發表於2024-10-21

點選此處,立即下載通義靈碼!https://tongyi.aliyun.com/lingma/

通義靈碼加持的單元測試實踐

本文首先講述了什麼是單元測試、單元測試的價值、一個好的單元測試所具備的原則,進而引入如何去編寫一個好的單元測試,通義靈碼是如何快速生成單元測試的。

什麼是單元測試?

單元測試是一種軟體測試方法,透過編寫程式碼來驗證應用程式中最小的可測試單元(如單個函式、方法或類)的正確性。通常,單元測試由開發人員在功能實現過程中或完成後編寫,其目的是確保每個最小可測試單元都能按照設計預期正常工作。

單元測試的價值

單元測試的價值主要體現在提高軟體質量和可靠性,‌確保程式碼在修改或重構後仍然能夠正常執行。‌單元測試的優勢包括:

  • 提高程式碼質量:‌透過發現程式碼中的錯誤和漏洞,‌從而提高程式碼的質量和可靠性。‌
  • 提高開發效率:‌在開發過程中及時發現問題,‌減少開發週期和成本。‌
  • 便於重構和維護:‌確保程式碼在重構和維護過程中不會出現新的錯誤和漏洞。‌
  • 有助於團隊協作:‌作為團隊成員之間的溝通和協作工具,‌提高團隊的協作效率和質量。‌

此外,‌單元測試還可以讓軟體故障儘早被發現,‌避免故障遺留到後期由於定位修復難度帶來的更大損失。‌它的可迴歸性為軟體提供了一層安全防護網,‌為軟體後續的重構和修改提供了安全保障。‌單元測試還為軟體單元如何被使用提供了天然的程式碼樣例使用手冊。

遵循的原則

好的單元測試就像空氣(AIR)一樣感覺不到,但在測試質量的保障上,卻是非常關鍵的。好的單元測試宏觀上來說,具有自動化(A)、獨立性(I)、可重複執行(R)的特點。

  • A: Automatic(自動化):單元測試應能被自動化執行,以便在程式碼更改時快速確認新加入的程式碼沒有破壞已有功能,通常情況下會將單測接入到持續整合中,每當程式碼有變更時都會透過持續整合自動觸發單元測試。
  • I: Independent(獨立性):每個單元測試應當是獨立的,不能依賴於其他測試的執行順序或結果。這也要求單測的測試顆粒度必須足夠小,只有這樣才能滿足獨立性。
  • R: Repeatable(可重複執行):好的單元測試在同樣的條件下,每次執行應當給出相同的結果。它不應依賴於外部因素(如網路、資料庫或檔案系統),需要對這些外部的依賴進行正確的mock 。

除此之外,好的單測還必須要滿足有明確的斷言,執行速度快,邊界測試充分,覆蓋率高等特點。只有滿足這些條件的單測才是好的單測,好的單元測試是對程式碼質量保障至關重要的一環。

如何編寫單元測試?

下面以Java語言為例來介紹如何編寫單元測試,總體遵循以下幾個步驟:

拆分詳細的測試用例

考慮分支條件

在編寫單元測試時,需要考慮程式碼中的所有分支。分支包括if、else、switch語句等,每個分支都需要單獨測試。例如:

public String classifyNumber(int number) {
    if (number < 0) {
        return "negative";
    } else if (number == 0) {
        return "zero";
    } else {
        return "positive";
    }
}

在上述程式碼中,有三個分支需要測試:

  • number<0。
  • number==0。
  • number>0。

針對每個分支,都需要編寫一個測試用例。

尋找邊界條件

除了分支,還需要考慮邊界條件。例如,對於上述分類函式,邊界值是-1、0和1。邊界條件測試可以找出可能潛在的問題,確保程式碼在極限條件下也能正常工作。

制定統一的單測規範

命名規範

單元測試類通常採用 類名Test的形式。例如,如果要測試一個Calculator類,則單元測試類可以命名為CalculatorTest。單元測試方法的命名應當描述具體要測試的內容。例如:

public class CalculatorTest {
    @Test
    public void testAddition() {
        // 測試內容
    }
    @Test
    public void testSubtraction() {
        // 測試內容
    }
}

存放路徑

一般情況下,單元測試類存放的位置在與被測試類相同包名的下面,但在不同的目錄中。在標準的 Maven 專案結構中,原始碼放在src/main/java,單元測試程式碼放在src/test/java。

src/main/java/com/example/Calculator.java
src/test/java/com/example/CalculatorTest.java

選擇合適的單測框架

Java 常用的單元測試框架有JUnit和Mockito,下面將圍繞如何使用這兩種框架編寫基本的單元測試:

JUnit 測試框架

JUnit是Java語言中最著名的單元測試框架。它簡潔易用,提供了豐富的註解和斷言。

  1. 新增JUnit依賴(以Maven為例):
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.13.2</version>
  <scope>test</scope>
</dependency>
  1. 編寫單元測試:
import org.junit.Test;
import static org.junit.Assert.*;

public class CalculatorTest {
    @Test
    public void testAddition() {
        Calculator calculator = new Calculator();
        assertEquals(5, calculator.add(2, 3));
    }
}

Mockito 測試框架

Mockito是一個強大的模擬框架,用於建立虛擬物件,簡化單元測試,特別是在測試不易建立的依賴物件時非常有用。

  1. 新增Mockito依賴:
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>3.11.2</version>
  <scope>test</scope>
</dependency>
  1. 編寫單元測試:
import static org.mockito.Mockito.*;
import org.junit.Test;

public class UserServiceTest {
    @Test
    public void testGetUser() {
        UserService userService = new UserService();
        UserRepository mockRepo = mock(UserRepository.class);

        when(mockRepo.findUserById(1)).thenReturn(new User(1, "John Doe"));
        userService.setUserRepository(mockRepo);

        User user = userService.getUserById(1);
        assertNotNull(user);
        assertEquals("John Doe", user.getName());
    }
}

如何使用通義靈碼快速生成單測

在大多數開發者的程式設計習慣中,通常採用Test Later的方法進行單元測試,即首先編寫好程式碼,然後再為程式碼編寫相應的單元測試。在此背景下,利用通義靈碼生成單元測試顯得尤為便捷。以下列舉幾種使用通義靈碼生成單元測試的方式。

手工選中程式碼方式生成

在 IDE 編輯區中,透過通義靈碼選中某段程式碼來生成單測,選中程式碼後,在靈碼的問答框內,透過 /generate unit test來生成與選中的程式碼對應的單元測試。

說明:使用/generate unit test這個命令時,可以在輸入框追加更多的資訊,來生成更符合開發者需要的測試用例,如需要支援JUnit5或採用Mockito進行Mock,就可以採用 /generate unit testJUnit5Mockito這樣的方式,後面兩個關鍵詞被當作前面命令的引數,這樣的使用方式,同樣適用於其他命令。

使用快捷按鈕方式生成

通義靈碼外掛在每個方法簽名的上面都會有一個小圖示,單擊圖示後會彈出相應的命令選單,選擇生成單元測試。

也可以選擇要生成的程式碼塊,然後單擊右鍵來選擇生成單元測試功能。

採納單元測試

單測程式碼生成完畢後,在問答區的程式碼塊右上角,會有三個選項,分別是插入程式碼,複製和新建檔案:

  • 插入程式碼:會將當前生成的單測程式碼插入到當前開啟的檔案中。
  • 複製:即複製程式碼塊的程式碼,由使用者自行選擇將程式碼複製到哪個檔案中。
  • 新建檔案:會按照Java單測的規範,新生成一個單測類檔案,放到指定的目錄中(對應單測方法檔案所在的test目錄下),如果已經存在同名的單測檔案,需要使用者自主確認是否覆蓋原檔案。

單測生成追問

如果您對當前生成的單測程式碼不滿意(需要使用特定的單測框架或者生成更多的單測方法),只需在問答區中的輸入框輸入相應的文字進行追問即可,或者也可以單擊預置的單測追問Tag,如示例中追問的Tag :Retry、Use Mockito、Use Spring Test和Explain code,進行追問,直到生成您滿意的單測程式碼為止。

說明:一般根據程式碼生成的單元測試,會列舉常用的測試用例,並不會遍歷所有的用例。如果您覺得生成的用例不足,建議先採納當前生成的幾個用例,放到測試檔案當中。然後,切換到測試檔案中,採用程式碼續寫的方式,通義靈碼會幫助您續寫新的測試用例。

結語

單元測試是重要的程式設計實踐,為編碼過程搭建質量圍欄。同時,採用測試驅動開發實踐中的Test First ,能夠顯著推動程式碼設計的演變。通義靈碼,可以極大降低單元測試框架的搭建和測試用例編寫的工作量。同時,使用通義靈碼對單元測試用例保鮮,可以顯著提升編碼質量。

用通義靈碼維護遺留程式碼的正確姿勢

本文首先介紹了遺留程式碼的概念,並對遺留程式碼進行了分類。針對不同型別的遺留程式碼,提供了相應的處理策略。此外,本文重點介紹了通義靈碼在維護遺留程式碼過程中能提供哪些支援。

什麼是遺留程式碼

  • 與過時技術相關的程式碼:
    • 與不再受支援的作業系統或軟體庫相關的程式碼。
    • 依賴於已淘汰的技術棧或程式語言的程式碼。
  • 為相容老舊功能而保留的程式碼:
    • 在現代軟體中為了相容舊版本功能而保留的程式碼片段。
    • 為了確保向後相容性而不得不保留的程式碼。
  • 缺乏文件和維護的程式碼:
    • 沒有良好文件支援的舊程式碼。
    • 缺乏現代開發實踐(如單元測試、程式碼審查等)的程式碼。

解決遺留程式碼的方法

解決遺留程式碼有以下三種常見的處理方法:

根據上述描述,補充單元測試是一種有效解決遺留程式碼問題的方法。然而,這種方法仍然存在一些問題:

  • 大量遺留程式碼缺少單元測試,並且由於程式碼間的複雜依賴關係,進行測試的成本非常高。
  • 具體的衡量標準卻不夠清晰,無法定義好的單元測試。
  • 哪些程式碼需要新增單元測試?

單元測試常見的誤區

  • 缺乏斷言的假單元測試:開發者可能會採取僅呼叫函式而不進行斷言的方式,以提高覆蓋率指標,導致了許多無效的單元測試。
  • 把單元測試當成白盒測試:一些觀點將單元測試歸類為白盒測試,但實際上應將其視為針對函式簽名的黑盒測試。
  • 依賴真實環境的單元測試:阻礙單元測試的主要因素包括惰性和依賴環境配置。若不使用Stub或Mock解除對外部環境(如網路IP、資料庫)的依賴,單元測試將難以達到FIRST原則(快速、獨立、可重複、自我驗證、及時性)。

選擇性的進行單元測試

單元測試除了帶來收益外,本身也會產生一定的成本。如果從收益與成本的角度分析遺留程式碼,將有助於明確為遺留程式碼補充單元測試的策略,此策略被稱為選擇性單元測試。那麼,如何界定成本與收益呢?

遺留程式碼單元測試的成本收益象限分類

針對遺留程式碼的單元測試,可以根據其成本和收益進行象限分類。根據下圖,對分類標準和各象限進行詳細說明:

分類標準

  • X軸(成本):程式碼依賴程度越高,測試成本越大。
  • Y軸(收益):程式碼複雜度越高,質量收益越大。

四個象限

圈複雜度與依賴的概念理解

  • 圈複雜度(Cyclomatic Complexity):衡量程式碼中邏輯分支的數量。
  • 扇入(Fan-In):直接呼叫該模組的上級模組的個數,扇入大表示模組的複用程度高。
  • 扇出(Fan-out):一個模組直接呼叫的其他模組的數量,扇出大表示該模組依賴其他模組越多。

不同型別程式碼的處理策略

根據上述的分析,遺留程式碼的處理策略就變得十分明確:

  • 演算法類程式碼(Algorithms Code):生成單元測試。
  • 協調類程式碼(Coordinators Code):進行介面測試。
  • 複雜程式碼(Overcomplicated Code):尋找合適的機會進行重構。
  • 瑣碎程式碼(Trivial Code):不做處理。

使用通義靈碼處理遺留程式碼

1. 瞭解專案工程

在維護一個工程的遺留程式碼,首先可透過 @workspace 功能瞭解整個工程的目的及其涉及的各個模組。

2. 對不同型別程式碼進行處理

針對演算法類(Algorithms)程式碼生成單元測試

選中需要基於生產程式碼進行程式碼生成的部分。在生成時,請注意所需的框架及Mock等依賴資訊,可以透過生成單元測試命令後追加相關資訊進行補充。如 /generate unit testingCppUTest。

一般而言,基於現有程式碼生成的單元測試用例數量通常較為有限。如果對單元測試的測試場景及用例數量有具體要求,可以在新生成的單元測試檔案中,透過測試函式的續寫方式生成更多的單元測試。在續寫過程中,通義靈碼將盡可能遵循已有用例,以此作為上下文進行參考。

針對協調類程式碼(Coordinators)進行介面測試

對於協調類程式碼而言,單元測試並不是一種理想的解決方案,由於存在過多的依賴,測試成本顯著提高。針對此類程式碼,應該採用介面測試或功能測試的方式進行覆蓋,然而在編寫自動化測試用例時,開發者常常會遇到相關問題。因此,可以透過通義靈碼,快速掌握並理解測試框架。

針對用例的編寫,可以選擇相應的被測函式,並在問答區中直接提問:基於Robot Framework生成以下程式碼的測試用例。沿著這一思路,同樣可以實現相應的Keywords等內容。

超複雜的程式碼(Overcomplicated Code)找機會進行重構

針對超複雜的程式碼,可以使用通義靈碼的 /generate optimization 命令,以獲得針對所選程式碼的最佳化建議。程式碼審查與最佳化將從語法問題、異常改進、程式碼整潔度、安全性及風險等多個維度給出相應的最佳化建議。

針對複雜遺留程式碼的最佳化,並不建議開發者們盲目進行全面的最佳化和重構。遺留程式碼的變更可能會帶來巨大的風險。因此,開發者應根據具體情況,在合適的機會,遵守童子軍法則,進行重構和最佳化。在這一過程中,通義靈碼也將為您提供有力支援。

結語

以上便是在處理遺留程式碼時可參考的實踐。處理遺留程式碼需要深入程式碼的複雜結構,細緻地追蹤每一個可能的分支節點。在這一過程中,除了識別並修復潛在的缺陷外,還必須在有限的時間內完成所有任務。為了避免這一局面的發生,最佳的策略是預防程式碼的腐化,善用工具,並在編寫初期遵循良好的程式設計原則。

相關文章