📜 ⬆️ ⬇️

Testing in F #

Introduction


You've probably already heard a lot of good things about the F # language, and even probably managed to try it out on small personal projects. But what about when it comes to something a little more than just running and debugging a simple console application or script? In this article I will tell you about my personal experience with tests in F #. Who cares, I ask in the tackle.



Source


For the convenience of presentation, I have prepared a small project, the source code of which is available here . Sources contain a small module and tests for it. This is the module itself:


[<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")) 

Library for testing


In principle, to test your applications on F # you can do without any special libraries. Although if you, like me, prefer a more standard approach, you can easily use such libraries as:



Here I will not go into details of what type of framework is the best in the world, I will leave it at your discretion. I prefer xUnit and will continue to use it, if your preferences do not coincide with mine, you can switch to your favorite library for testing.


So first, add the xunit and xunit.runner.visualstudio packages to your project .



Assert library


Each small test framework provides you with a minimum set of assert-functions. In principle, they are enough in 90% of cases, but they are not very convenient to use. Let's look at a couple of additional and convenient libraries.



Mock library


If you are faced with the need to use Mock for testing, you can use Moq , but if you are looking for a little more F # friendly solution, you can use Foq . Let's compare these two libraries in use.
')
Calling a method in Moq:


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

Calling a method in Foq:


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

Comparison of arguments in Moq:


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

Comparison of arguments in Foq:


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

Property in Moq:


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

Property in Foq:


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

Other Utility


Depending on your needs, you can also use the well-known “Arrange phase minimizer” and the stub generator - AutoFixture . You can also take advantage of other AutoFixture integration benefits with xUnit .


Writing tests


So, when everything is ready, you can proceed to writing tests. xUnit allows us to use both standard classes and the definition of modules in F #, you decide which approach suits you best. Below are examples of two approaches.


Class:


 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:


 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 attempted`` () = raises<InvalidOperationException> <@ metersToHours 2.0<m> @ 

Instead of conclusion


The above tests run safely in the studio and on the integration server. Thank you for attention. I hope you found this article helpful.

Source: https://habr.com/ru/post/280410/


All Articles