本教學課程會逐步引導您完成建置範例解決方案的互動式體驗,以瞭解單元測試概念。
本文是關於測試 .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
您已建立一個小型程式庫及其一組單元測試。 您已建構解決方案,讓新增套件和測試成為一般工作流程的一部分。 您將大部分時間和精力集中在解決應用程序的目標上。