單元測試篇2-TDD三大法則解密

董瑞鹏發表於2024-04-07

引言

在我們上一篇文章瞭解了單元測試的基本概念和用法之後,今天我們來聊一下 TDD(測試驅動開發)

測試驅動開發 (TDD)

測試驅動開發英文全稱是Test Driven Development 簡稱 TDD。

根據 UncleBob 的 TDD 描述總結

我們先建立一個測試專案

直接在 VS 建立即可,可以參考上一篇文章的建立過程

The Three Laws of TDD.

  • You are not allowed to write any production code unless it is to make a failing unit test pass.
  • You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
  • You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

這是本文的描述的三個 TDD 開發的原則,它確保了程式碼的質量和可維護性。

下面對這三條內容做詳細的解釋

  • 第一條規則指出 不允許編寫任何的生產程式碼,除非是在讓單元測試透過時。
    • 簡單理解就是在編寫任何實際的業務邏輯程式碼之前,必須先編寫一個或者多個單元測試,這些單元測試因為沒有實現所以會失敗,有了失敗的單元測試之後我們才可以去在生產程式碼中實現業務邏輯
  • 第二條規則指出 不允許編寫比失敗所需更多的單元測試程式碼;編譯失敗也是失敗:

    • 這可以理解為 在編寫單元測試時,應該只編寫足夠使測試失敗的最小程式碼量。這樣,可以立即知道新寫的生產程式碼是否解決了問題。編譯失敗同樣被視為測試失敗,因為編譯不透過意味著程式碼無法執行。

那一個我們上一章節的一個數學計算類的例子


namespace dotNetParadise_TDD.Test;

public class MathCalculatorTests
{
    [Fact]
    public void Add_TwoNumbers_ReturnSum()
    {
        // Arrange
        var calculator = new MathCalculator();

        // Act
        var result = calculator.Add(3, 5);

        // Assert
        Assert.Equal(8, result);
    }
}

因為我們沒有 MathCalculator 這個類的實現所以,程式碼會編譯失敗。

image

在這個示例中,我們展示瞭如何編寫一個簡單的單元測試,測試 Calculator 類的 Add 方法是否能夠正確地將兩個數字相加並返回正確的結果。根據 TDD 原則,我們只編寫了必要部分的程式碼來測試這個功能,並且在這個階段測試應該會失敗,因為 Add 方法還未實現。編譯失敗也會被視為測試失敗,這強調了編寫足夠簡潔和精確的單元測試的重要性,符合第二條準則。

  • 第三條規則指出 不允許編寫比透過單個失敗單元測試所需更多的生產程式碼
    可以理解為在編寫生產程式碼時,只需編寫足夠讓失敗的單元測試透過的程式碼,而不是一次性編寫完整的功能。這有助於保持程式碼的小步前進,並且每次更改都有明確的測試驗證。

現在把MathCalculator類中增加一個引數*2 即翻倍的一個功能

第一步編寫一個單元測試方法,

    [Fact]
    public void DoubleNumber_WhenGivenSingleNumber_ReturnsDouble()
    {
        // Arrange
        var calculator = new MathCalculator();

        // Act
        var result = calculator.DoubleNumber(2);

        // Assert
        Assert.Equal(4, result);
    }

第二步 編寫足夠讓失敗的單元測試透過的程式碼

namespace dotNetParadise_TDD.Test;

public class MathCalculator
{
    public int DoubleNumber(int number)
    {
        throw new NotImplementedException();
    }
}

接下來執行單元測試

 dotNetParadise_TDD.Test.MathCalculatorTests.DoubleNumber_WhenGivenSingleNumber_ReturnsDouble
   源: MathCalculatorTests.cs 行 20
   持續時間: 371 毫秒

  訊息: 
System.NotImplementedException : The method or operation is not implemented.

  堆疊跟蹤: 
MathCalculator.DoubleNumber(Int32 number) 行 7
MathCalculatorTests.DoubleNumber_WhenGivenSingleNumber_ReturnsDouble() 行 26
RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

結果和預期一樣,測試沒有成功

image

現在來重構一下這個方法

    public int DoubleNumber(int number)
    {
        //throw new NotImplementedException();
        return 2 * number;
    }

再次執行單元測試

image

可以看到單元測試成功了!

TDD 開發流程圖

最後

通常我們進行單元測試的時候都是先寫業務邏輯,然後再單元測試,當系統業務邏輯變複雜之後可能會遺漏一些測試 CaseTDD 的出現就是解決這個問題,透過測試 Case 來寫重構業務程式碼的模式。

這三個規則確保了 TDD 的核心迴圈:紅(測試失敗)、綠(測試透過)、重構。透過不斷地重複這個過程,開發者能夠編寫出高質量、可測試、易於維護的程式碼。

本文完整原始碼
image

相關文章