Тестирование в F#

Введение


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

Исходный код


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

[]
module DistanceUnits

open System

[] type m
[] type cm
[] type inch
[] type ft
[] type h

let mPerCm : float = 0.01
let cmPerInch : float = 2.54
let inchPerFeet: float = 12.0

let metersToCentimeters (x: float) = x / mPerCm
let centimetersToInches (x: float) = x / cmPerInch
let inchesToFeets (x:float) = x / inchPerFeet

let centimetersToMeters: float -> float = ( * ) mPerCm
let inchesToCentimeters: float -> float = ( * ) cmPerInch
let metersToInches: float -> float = metersToCentimeters >> centimetersToInches
let metersToFeets: float -> float =  metersToInches >> inchesToFeets
let feetsToInches: float -> float = ( * ) inchPerFeet
let metersToHours(m: float): int = raise(new InvalidOperationException("Unsupported operation"))


Библиотека для тестирования


В принципе для тестирования ваших приложений на F# вы можете обойтись без каких либо специальных библиотек. Хотя если вы, как и я, предпочитаете более стандартный подход, то вы без проблем сможете воспользоваться такими библиотеками как:
Здесь я не буду вдаваться в детали типа какой фреймворк самый лучший в мире, оставлю это на ваше усмотрение. Я отдаю предпочтение xUnit и далее буду использовать его, если ваши предпочтения не совпадают с моими то вы лего можете переключиться на вашу любимую библиотеку для тестирования.
Итак для начала добавьте в ваш проект пакеты xunit и xunit.runner.visualstudio

Assert библиотеки


Каждый маломальский тестовый фреймворк предоставляет вам минимальный набор assert-функций. В принципе их хватает в 90% случаев, но ими не совсем удобно пользоваться. Давайте рассмотрим парочку дополнительных и удобных библиотек.

  • Fluent Assertions Интересное решение для любителей цепочных вызовов. Неплохо работает в C#, но к сожалению неуклюже в F#, так как цепочка никогда не возвращает Unit, следовательно вам нужно всегда изворачиваться и писать что-то вроде actual.Should().StartWith("S") |> ignore.
  • FsUnit Библиотека написанная специально для F#, но заточенная изначально под NUnit. С примерами вы можете ознакомиться здесь. Имеет в наличие поддержку xUnit, но поддержка это выглядит ограниченно и поддерживается слабовато, а жаль.
  • Unquote Довольно интересное решение, использующее Quoted Expressions. Единственное, на мой взгляд, ограничение заключается в зависимости от F# версии 4.0 и выше. Далее в этой статье я буду использовать именно эту библиотеку.


Mock библиотеки


Если вы сталкиваетесь с необходимостью использовать Mock-и для тестирования вы можете воспользоваться Moq, но если вы ищите немного более F#-дружественного решения, вы можете воспользоваться Foq. Давайте сравним в использовании эти две библиотеки.

Вызов метода в Moq:

var mock = new Mock();
mock.Setup(foo => foo.DoIt(1)).Returns(true);
var instance = mock.Object;


Вызов метода в Foq:

let foo = Mock()
            .Setup(fun foo -> <@ foo.DoIt(1) @>).Returns(true)
            .Create()


Сравнение аргументов в Moq:

mock.Setup(foo => foo.DoIt(It.IsAny())).Returns(true);


Сравнение аргументов в Foq:

mock.Setup(fun foo -> <@ foo.DoIt(any()) @>).Returns(true)


Свойство в Moq:

mock.Setup(foo => foo.Name ).Returns("bar");


Свойство в Foq:

mock.Setup(fun foo -> <@ foo.Name @>).Returns("bar")


Другие полезности


В зависимости от ваших нужд, вы так же можете воспользоваться известным «минимизатором Arrange фазы тестирования» и генератором заглушек — AutoFixture. Так же вы можете воспользоваться другими полезностями интеграции AutoFixture с xUnit.

Написание тестов


Итак, когда все готово, можно перейти к написанию тестов. xUnit позволяет нам использовать как стандартные классы так и определение модулей в F#, вам решать какой подход вам больше подходит. Ниже представлены примеры двух подходов.

Класс:

type ConverterTest1() =
    []
    member me.``It should convert meters to centimeters as expected``() =

        let actual = 1100.0 |> centimetersToMeters

        test <@ actual = 11.0 @>

    []
    member me.``It should convert centimeters to meters as expected``() =
        let actual = 20.0 |> metersToCentimeters

        test <@ actual = 2000.00 @>


Модуль:

module ConverterTest2 =
    open System
    []
    let ``It should convert meters to feets as expected`` () =
        let actual =  32.0 |> metersToFeets

        test <@ actual = 104.98687664041995 @>

    []
    let ``It should fail when rubbish conversion is attempted`` () =
        raises <@ metersToHours 2.0 @


Вместо заключения


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

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

© Habrahabr.ru