使用 dotnet test 和 MSTest 在 .NET Core 中对 F# 库进行单元测试

本教程逐步讲解了构建示例解决方案的交互式体验,以学习单元测试概念。 如果希望使用预构建解决方案学习本教程,请在开始前查看或下载示例代码。 有关下载说明,请参阅 示例和教程

本文介绍如何测试 .NET Core 项目。 如果要测试 ASP.NET Core 项目,请参阅 ASP.NET Core中的 集成测试。

创建源项目

打开 shell 窗口。 创建名为 unit-testing-with-fsharp 的目录来保存解决方案。 在此新目录中,运行 dotnet new sln 创建新的解决方案。 这样,可以更轻松地管理类库和单元测试项目。 在解决方案目录中,创建 MathService 目录。 到目前为止,目录和文件结构如下所示:

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

使 MathService 成为当前目录并运行 dotnet new classlib -lang "F#" 以创建源项目。 创建数学服务的失败实现:

module MyMath =
    let squaresOfOdds xs = 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.sln
    /MathService
        Source Files
        MathService.fsproj
    /MathService.Tests

MathService.Tests 目录设为当前目录,并使用 dotnet new mstest -lang "F#" 创建新项目。 这会创建一个使用 MSTest 作为测试框架的测试项目。 生成的模板在 MathServiceTests.fsproj 中配置测试运行程序:

<ItemGroup>
  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170628-02" />
  <PackageReference Include="MSTest.TestAdapter" Version="1.1.18" />
  <PackageReference Include="MSTest.TestFramework" Version="1.1.18" />
</ItemGroup>

测试项目需要其他包来创建和运行单元测试。 dotnet new 在上一步中添加了 MSTest。 现在,将 MathService 类库添加为项目的另一个依赖项。 使用 dotnet reference add 命令:

dotnet reference add ../MathService/MathService.fsproj

可以在 GitHub 上的 示例存储库 中看到整个文件。

你有以下最终解决方案布局:

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

dotnet sln add .\MathService.Tests\MathService.Tests.fsproj 目录中执行

创建第一个测试

编写一个失败测试,使其通过,然后重复此过程。 打开 Tests.fs 并添加以下代码:

namespace MathService.Tests

open System
open Microsoft.VisualStudio.TestTools.UnitTesting
open MathService

[<TestClass>]
type TestClass () =

    [<TestMethod>]
    member this.TestMethodPassing() =
        Assert.IsTrue(true)

    [<TestMethod>]
     member this.FailEveryTime() = Assert.IsTrue(false)

[<TestClass>] 属性表示包含测试的类。 该 [<TestMethod>] 属性表示由测试运行程序运行的测试方法。 在 unit-testing-with-fsharp 目录中,执行 dotnet test 以生成测试和类库,然后运行测试。 MSTest 测试运行程序包含要运行测试的程序入口点。 dotnet test 使用已创建的单元测试项目启动测试运行程序。

这两个测试展示了最基本的通过和失败情况。 My test 通过,而 Fail every time 失败。 现在,为 squaresOfOdds 该方法创建一个测试。 该方法 squaresOfOdds 返回输入序列中所有奇数整数值的平方的列表。 可以迭代创建验证功能的测试,而不是一次性编写所有这些函数。 使每个测试通过都意味着为该方法创建必要的功能。

我们可以写入的最简单测试是使用所有偶数进行调用 squaresOfOdds ,其中结果应该是一个空的整数序列。 下面是测试:

[<TestMethod>]
member this.TestEvenSequence() =
    let expected = Seq.empty<int> |> Seq.toList
    let actual = MyMath.squaresOfOdds [2; 4; 6; 8; 10]
    Assert.AreEqual(expected, actual)

请注意,序列 expected 已转换为列表。 MSTest 库依赖于许多标准 .NET 类型。 该依赖项意味着你的公共接口和预期结果支持 ICollection ,而不是 IEnumerable

运行测试时,会看到测试失败。 尚未创建实现。 在起作用的 Mathservice 类中编写最简单的代码,使此测试通过:

let squaresOfOdds xs =
    Seq.empty<int> |> Seq.toList

unit-testing-with-fsharp 目录中,再次运行 dotnet testdotnet test 命令构建 MathService 项目,然后构建 MathService.Tests 项目。 生成这两个项目后,它将运行此单个测试。 测试通过。

完成要求

你已经通过了一个测试,现在可以编写更多测试。 下一个简单的事例适用于唯一奇数为 1的序列。 数字 1 更容易,因为 1 的正方形为 1。 下面是下一个测试:

[<TestMethod>]
member public this.TestOnesAndEvens() =
    let expected = [1; 1; 1; 1]
    let actual = MyMath.squaresOfOdds [2; 1; 4; 1; 6; 1; 8; 1; 10]
    Assert.AreEqual(expected, actual)

执行 dotnet test 时,新测试失败。 必须更新 squaresOfOdds 该方法才能处理此新测试。 必须筛选序列中的所有偶数,以使此测试通过。 为此,可以编写一个小型筛选器函数并使用 Seq.filter

let private isOdd x = x % 2 <> 0

let squaresOfOdds xs =
    xs
    |> Seq.filter isOdd |> Seq.toList

注意对 Seq.toList 的调用。 这会创建一个列表,用于实现 ICollection 接口。

还要执行一个步骤:计算每个奇数的平方值。 首先编写新的测试:

[<TestMethod>]
member public this.TestSquaresOfOdds() =
    let expected = [1; 9; 25; 49; 81]
    let actual = MyMath.squaresOfOdds [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
    Assert.AreEqual(expected, actual)

可以通过映射操作传递经过筛选的序列来计算每个奇数的平方,以此方式来修复测试的缺陷:

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

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

你已为该库生成了一个小库和一组单元测试。 你已构建解决方案,以便添加新包和测试是正常工作流的一部分。 你大部分时间都专注于解决应用程序的目标。

另请参阅