Модульное тестирование библиотек F# с помощью dotnet test и TUnit

В этом руководстве вы познакомитесь с интерактивным процессом пошагового создания примерного решения, чтобы изучить концепции модульного тестирования.

В этой статье описывается тестирование проекта .NET Core. Если вы тестируете проект ASP.NET Core, см. тесты интеграции в ASP.NET Core.

Prerequisites

  • SDK .NET 10 или более поздних версий.
  • Текстовый редактор или редактор кода по вашему выбору.

TUnit полностью построен на Microsoft. Testing.Platform (MTP) и не поддерживает VSTest. В этом руководстве используется режим dotnet testMTP, для которого требуется пакет SDK .NET 10.

Создание исходного проекта

Откройте окно терминала. Создайте каталог с именем 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 не включены в пакет SDK .NET. Установите их с помощью следующей команды:

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, в MathService.Tests.fsproj следующим образом:

<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

Настройка режима MTP для dotnet test

Так как TUnit не поддерживает VSTest, dotnet test должен выполняться в режиме MTP. В каталоге unit-testing-with-fsharp создайте файл global.json со следующим содержимым:

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

Дополнительные сведения см. в режиме MTP dotnet test.

Создание первого теста

Вы пишете один не проходящий тест, делаете его успешным, а затем повторяете процесс. Замените содержимое 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 являются асинхронными. Выражение вычисления taskAssert из TUnit.Assertions.FSharp запускает тест как задачу и позволяет ожидать каждую проверку с помощью 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 проверяет, содержат ли обе последовательности одинаковые элементы.

При запуске теста вы увидите, что тест завершается сбоем. Это связано с тем, что вы еще не создали реализацию. Выполните этот тест, написав самый простой код в файле Library.fs в проекте MathService, который работает:

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

Вы создали небольшую библиотеку и набор модульных тестов для этой библиотеки. Вы структурировали решение таким образом, чтобы добавление новых пакетов и тестов было частью обычного рабочего процесса. Вы сосредоточили большую часть времени и усилий на решении целей приложения.

См. также