使用 dotnet test 和 TUnit 進行 F# 函式庫的單元測試

本教學課程會逐步引導您完成建置範例解決方案的互動式體驗,以瞭解單元測試概念。

本文是關於測試 .NET Core 專案。 如果您要測試 ASP.NET Core 專案,請參閱 ASP.NET Core 中的整合測試。

Prerequisites

  • .NET 10 SDK 或更新版本。
  • 您偏好的文字編輯器或程式碼編輯器。

TUnit 完全建置於 Microsoft.Testing.Platform (MTP) 之上,且不支援 VSTest。 本教學使用 MTP 模式dotnet test,需 .NET 10 SDK。

建立來源專案

開啟終端機視窗。 建立名為 unit-testing-with-fsharp 的目錄來保存解決方案。 在此新目錄內,執行下列命令,為類別庫和測試專案建立新的方案檔:

dotnet new sln

接下來,建立 MathService 目錄。 下列大綱顯示到目前為止的目錄和檔案結構:

/unit-testing-with-fsharp
    unit-testing-with-fsharp.slnx
    /MathService

將 MathService 設為目前的目錄,然後執行下列命令來建立來源專案:

dotnet new classlib -lang "F#"

將 Library.fs 的內容替換為一個會失敗的數學服務實作:

namespace MathService

module MyMath =
    let squaresOfOdds (xs: int seq) : int seq = raise (System.NotImplementedException("You haven't written a test yet!"))

將目錄變更回 unit-testing-with-fsharp 目錄。 執行下列命令,將類別庫專案新增至方案:

dotnet sln add ./MathService/MathService.fsproj

建立測試專案

接下來,建立 MathService.Tests 目錄。 下列大綱顯示目錄結構:

/unit-testing-with-fsharp
    unit-testing-with-fsharp.slnx
    /MathService
        Source Files
        MathService.fsproj
    /MathService.Tests

TUnit 專案範本不包含在 .NET SDK 中。 請使用以下指令安裝:

dotnet new install TUnit.Templates

將 MathService.Tests 目錄設為目前目錄,並使用下列命令建立新專案:

dotnet new TUnit -lang "F#"

Note

模板短名稱具有大小寫區分。 使用 TUnit,而不是 tunit。

此指令會建立一個測試專案,使用 TUnit 作為測試框架。 產生的 MathService.Tests.fsproj 檔案是一個可執行專案,會參考 TUnit 套件,其中包含測試框架、斷言函式庫和 MTP 測試執行器,以及 TUnit.Assertions.FSharp 套件,其可新增 F# 的斷言輔助程式。

範本還新增了範例測驗。 這個教學會用 Tests.fs 裡的獨立測試取代它們。 刪除 Data 目錄以及 GlobalSetup.fs、Tests2.fs 和 Tests3.fs 檔案。 F# 專案會明確列出其原始程式檔,因此也請將 ItemGroup 中包含 項目的 Compile 替換為以下內容:

<ItemGroup>
  <Compile Include="Tests.fs" />
</ItemGroup>

現在,將 MathService 類別庫新增為專案的另一個相依性。 使用 dotnet reference add 命令:

dotnet reference add ../MathService/MathService.fsproj

下列大綱顯示最終解決方案配置:

/unit-testing-with-fsharp
    unit-testing-with-fsharp.slnx
    /MathService
        Source Files
        MathService.fsproj
    /MathService.Tests
        Test Source Files
        MathService.Tests.fsproj

在 unit-testing-with-fsharp 目錄中執行下列命令:

dotnet sln add ./MathService.Tests/MathService.Tests.fsproj

設定 dotnet 測試的 MTP 模式

因為 TUnit 不支援 VSTest,dotnet test 必須以 MTP 模式執行。 在 unit-testing-with-fsharp 目錄中,建立一個包含以下內容的 global.json 檔案:

{
    "test": {
        "runner": "Microsoft.Testing.Platform"
    }
}

欲了解更多資訊,請參閱 dotnet 測試的 MTP 模式。

建立第一個測試

您撰寫一個失敗的測試,讓它通過,接著重複這個過程。 請將 Tests.fs 的內容替換為以下程式碼:

namespace MathService.Tests

open TUnit.Core
open TUnit.Assertions
open TUnit.Assertions.Extensions
open TUnit.Assertions.FSharp.TaskAssert
open MathService

type TestClass () =

    [<Test>]
    member this.TestMethodPassing() = taskAssert {
        do! Assert.That(true).IsTrue()
    }

    [<Test>]
    member this.FailEveryTime() = taskAssert {
        do! Assert.That(false).IsTrue()
    }

[<Test>] 屬性表示測試執行器所執行的測試方法。 TUnit 會發現屬於某類別的測試方法;它不會發現模組中宣告的 let 函式。 TUnit 的斷言是非同步的。 來自 TUnit.Assertions.FSharp 的 taskAssert 計算式會將測試作為任務執行,並讓你用 do! 等待每個斷言。

從 unit-testing-with-fsharp 目錄,執行 dotnet test 來建置測試和類別庫,然後執行測試。 TUnit 測試專案包含執行測試的程式入口點。 dotnet test 用你建立的單元測試專案啟動測試應用程式。

這兩個測試顯示了最基本的通過測試和失敗測試的結果。 TestMethodPassing 通過,而 FailEveryTime 失敗。 現在,為 squaresOfOdds 方法建立測試。 squaresOfOdds 方法會傳回屬於輸入序列之所有奇數整數值的平方的一個序列。 您可以反覆建立驗證功能的測試,而不是一次嘗試撰寫所有這些函式。 為了讓每次測試通過,你必須建立方法所需的功能。

您可以撰寫的最簡單測試是使用所有偶數呼叫 squaresOfOdds ,其中結果應該是空的整數序列。 將以下測試加入 TestClass 類型:

[<Test>]
member this.TestEvenSequence() = taskAssert {
    let expected = Seq.empty<int>
    let actual = MyMath.squaresOfOdds [2; 4; 6; 8; 10]
    do! Assert.That<int>(actual).IsEquivalentTo(expected)
}

F# 在解析方法多載時比 C# 更嚴格,因此測試會將項目類型傳遞給 Assert.That<int>,以選擇整數序列的斷言。 IsEquivalentTo 檢查兩個序列是否包含相同項目。

當您執行測試時,您會看到測試失敗。 這是因為您尚未建立實作。 透過在你的 MathService 專案中 Library.fs 檔案中寫出最簡單且有效的程式碼,讓這個測試通過:

let squaresOfOdds xs =
    Seq.empty<int>

在 unit-testing-with-fsharp 目錄中,再次執行 dotnet test。 dotnet test 命令會執行 MathService 項目的組建,然後執行 MathService.Tests 項目的組建。 建置這兩個項目之後,它會執行您的測試。 現在有兩個測試通過。

完成需求

既然您已通過一次測試,現在是時候撰寫更多了。 下一個簡單案例適用於只有奇數 1的序列。 數位 1 比較容易,因為 1 的平方是 1。 以下是下一個測試:

[<Test>]
member public this.TestOnesAndEvens() = taskAssert {
    let expected = [1; 1; 1; 1]
    let actual = MyMath.squaresOfOdds [2; 1; 4; 1; 6; 1; 8; 1; 10]
    do! Assert.That<int>(actual).IsEquivalentTo(expected)
}

執行 dotnet test 會讓新的測試失敗。 您必須更新 squaresOfOdds 方法來處理這個新的測試。 您必須篩選序列中的所有偶數,才能通過此測試。 您可以藉由撰寫小型篩選函式並使用 Seq.filter來執行此動作:

let private isOdd x = x % 2 <> 0

let squaresOfOdds xs =
    xs
    |> Seq.filter isOdd

還有一步要完成:將每個奇數取平方。 從撰寫新的測試開始:

[<Test>]
member public this.TestSquaresOfOdds() = taskAssert {
    let expected = [1; 9; 25; 49; 81]
    let actual = MyMath.squaresOfOdds [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
    do! Assert.That<int>(actual).IsEquivalentTo(expected)
}

您可以透過映射函數將篩選後的序列運算,以計算每個奇數的平方來修正測試:

let private square x = x * x
let private isOdd x = x % 2 <> 0

let squaresOfOdds xs =
    xs
    |> Seq.filter isOdd
    |> Seq.map square

您已建立一個小型程式庫及其一組單元測試。 您已建構解決方案,讓新增套件和測試成為一般工作流程的一部分。 您將大部分時間和精力集中在解決應用程序的目標上。

也請參閱