Тестування в F#

Введення
Ви напевно вже чули багато хорошого про мову F#, і навіть напевно встигли випробувати його на невеликих особистих проектах. Але як бути якщо мова йде про дещо більше ніж просто запуск і налагодження простого консольного програми або скрипту? У цій статті я розповім вам про мій особистий досвід роботи з тестами в F#. Кому цікаво, прошу в підкат.

Вихідний код
Для зручності викладення матеріалу я підготував невеликий проект, вихідний код якої доступний тут. Вихідні матеріали містять невеликий модуль і тести до нього. Ось власне і сам модуль:
[<AutoOpen>]
module DistanceUnits

open System

[<Measure>] type m
[<Measure>] type cm
[<Measure>] type inch
[<Measure>] type ft
[<Measure>] type h

let mPerCm : float<m/cm> = 0.01<m/cm>
let cmPerInch : float<cm/inch> = 2.54<cm/inch>
let inchPerFeet: float<inch/ft> = 12.0<inch/ft>

let metersToCentimeters (x: float<m>) = x / mPerCm
let centimetersToInches (x: float<cm>) = x / cmPerInch
let inchesToFeets (x:float<inch>) = x / inchPerFeet

let centimetersToMeters: float<cm> -> float<m> = ( * ) mPerCm
let inchesToCentimeters: float<inch> -> float<cm> = ( * ) cmPerInch
let metersToInches: float<m> -> float<inch> = metersToCentimeters >> centimetersToInches
let metersToFeets: float<m> -> float<ft> = metersToInches >> inchesToFeets
let feetsToInches: float<ft> -> float<inch> = ( * ) inchPerFeet
let metersToHours(m: float<m>): int<h> = 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<IFoo>();
mock.Setup(foo => foo.DoIt(1)).Returns(true);
var instance = mock.Object;

Виклик методу в Foq:
let foo = Mock<IFoo>()
.Setup(fun foo -> <@ foo.DoIt(1) @>).Returns(true)
.Create()

Порівняння аргументів у Moq:
mock.Setup(foo => foo.DoIt(It.IsAny<int>())).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() =
[<Fact>]
member me.`It should convert meters to centimeters as expected`() =

let actual = 1100.0<cm> |> centimetersToMeters

test <@ actual = 11.0<m> @>

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

test <@ actual = 2000.00<cm> @>

Модуль:
module ConverterTest2 =
open System
[<Fact>]
let `It should convert meters to feets as expected` () =
let actual = 32.0<m> |> metersToFeets

test <@ actual = 104.98687664041995<ft> @>

[<Fact>]
let `It should fail when rubbish conversion is спроба` () =
raises<InvalidOperationException> <@ metersToHours 2.0<m> @

Замість висновку
Вище наведені тести благополучно запускаються в студії і на інтеграційному сервері. Дякую за увагу. Сподіваюся вам ця стаття була корисною.

Потрібні unit-тести, в їх класичному розумінні, функціональних мовах програмування

/>
/>


<input type=«checkbox» id=«vv72332»
class=«checkbox js-field-data»
name=«variant[]»
value=«72332» />
Так, звичайно
<input type=«checkbox» id=«vv72334»
class=«checkbox js-field-data»
name=«variant[]»
value=«72334» />
Потрібно щось інше, більш близьке до ФП
<input type=«checkbox» id=«vv72336»
class=«checkbox js-field-data»
name=«variant[]»
value=«72336» />
Ні, навіщо взагалі паритися

Проголосувало 14 осіб. Утрималося 6 чоловік.


Пишіть ви тести коли працюєте з функціональними мовами програмування

/>
/>

<input type=«checkbox» id=«vv72338»
class=«checkbox js-field-data»
name=«variant[]»
value=«72338» />
Природно
<input type=«checkbox» id=«vv72340»
class=«checkbox js-field-data»
name=«variant[]»
value=«72340» />
Не бачу сенсу

Проголосувало 13 осіб. Утрималося 6 чоловік.


Тільки зареєстровані користувачі можуть брати участь в опитуванні. Увійдіть, будь ласка.


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.