Saturday, September 14, 2013

Should.BeAvailableInF#

Since I first heard of it, I've found the Should Assertions library convenient for the driver programs that stand in for more formal unit tests for code fragments that I want to blog. However, such main programs have to be written in C#, because you can't access C# extension methods on generic open types from F# -- which is exactly what the general run of Should extensions are.

So, you could write F# code like

Should.ObjectAssertExtensions.ShouldEqual(outstring, instring)
view raw gistfile1.fs hosted with ❤ by GitHub

but that is pretty ugly. And F# type extensions are defined against a specific class, so won't be any use. Which leaves us with generic functions as the best way to wrap the C# extension methods, but

shouldEqual outstring instring
view raw gistfile1.txt hosted with ❤ by GitHub

isn't much of an improvement.

The approach that brings us closer to the C# feel would be to make these methods infix operators.

In an ideal world, we'd use ⊦ (U+22A6 ASSERTION) as a lead character, but that's not yet available without forking the compiler, so I chose to write the functions in the form |-{op}, doubling the first character of the {op} for the versions that take custom comparison objects, adding a trailing % for variants taking a message, and using !- as an introducer for unary operations like !-/, where / alludes to ∅ (U+2205 EMPTY SET) for ShouldBeNull.

Apart from the object/generic assertion, the other type that needs transformation across the language barrier is the ShouldThrow<T> extension on Action, which can be wrapped as a generic function taking a unit -> unit, which uses that to create a delegate for the C# world.

Putting it all together we have module fhould, a pun on f for F# and for the old-fashioned cursive long 's' as in ſhould:

namespace Tinesware.FSharp
open System
open System.Collections.Generic
open Should
module fhould =
let inline (|->) x y =
ObjectAssertExtensions.ShouldBeGreaterThan(x, y)
let inline (|->>) (x:'a) (y:'a) (z:IComparer<'a>) =
ObjectAssertExtensions.ShouldBeGreaterThan(x, y, z)
let inline (|->=) x y =
ObjectAssertExtensions.ShouldBeGreaterThanOrEqualTo(x, y)
let inline (|->>=) (x:'a) (y:'a) (z:IComparer<'a>) =
ObjectAssertExtensions.ShouldBeGreaterThanOrEqualTo(x, y, z)
let inline (|-<>) x (y, z) =
ObjectAssertExtensions.ShouldBeInRange(x, y, z)
let inline (|-<<>>) (x:'a) (y:'a * 'a) (z:IComparer<'a>) =
ObjectAssertExtensions.ShouldBeInRange(x, fst y, snd y, z)
let inline (|-<) x y =
ObjectAssertExtensions.ShouldBeLessThan(x, y)
let inline (|-<<) (x:'a) (y:'a) (z:IComparer<'a>) =
ObjectAssertExtensions.ShouldBeLessThan(x, y, z)
let inline (|-<=) x y =
ObjectAssertExtensions.ShouldBeLessThanOrEqualTo(x, y)
let inline (|-<<=) (x:'a) (y:'a) (z:IComparer<'a>) =
ObjectAssertExtensions.ShouldBeLessThanOrEqualTo(x, y, z)
let inline (!-/) x =
ObjectAssertExtensions.ShouldBeNull(x)
let inline (|-===) x y =
ObjectAssertExtensions.ShouldBeSameAs(x, y)
let inline (|-@) x y =
ObjectAssertExtensions.ShouldBeType(x, y)
let inline (|-<@) x (y:Type) =
ObjectAssertExtensions.ShouldImplement(x, y)
let inline (|-<@%) x y z =
ObjectAssertExtensions.ShouldImplement(x, y, z)
let inline (|-=) x y =
ObjectAssertExtensions.ShouldEqual(x, y)
let inline (|-=%) x y (s:String)=
ObjectAssertExtensions.ShouldEqual(x, y, s)
let inline (|-==) (x:'a) (y:'a) (z:IEqualityComparer<'a>)=
ObjectAssertExtensions.ShouldEqual(x, y, z)
let inline (|-><) x (y, z) =
ObjectAssertExtensions.ShouldNotBeInRange(x, y, z)
let inline (|->><<) (x:'a) (y:'a * 'a) (z:IComparer<'a>) =
ObjectAssertExtensions.ShouldNotBeInRange(x, fst y, snd y, z)
let inline (!-?) x =
ObjectAssertExtensions.ShouldNotBeNull(x) |> ignore
let inline (|-?%) x y =
ObjectAssertExtensions.ShouldNotBeNull(x, y) |> ignore
let inline (|-!==) x y =
ObjectAssertExtensions.ShouldNotBeSameAs(x, y)
let inline (|-!@) x y =
ObjectAssertExtensions.ShouldNotBeType(x, y)
let inline (|-!=) x y=
ObjectAssertExtensions.ShouldNotEqual(x, y)
let inline (|-!!=) (x:'a) (y:'a) (z:IEqualityComparer<'a>)=
ObjectAssertExtensions.ShouldNotEqual(x, y, z)
let shouldThrow<'a when 'a :> Exception> f =
ActionAssertionExtensions.ShouldThrow<'a>(new Should.Core.Assertions.Assert.ThrowsDelegate(f))
view raw fhould.fs hosted with ❤ by GitHub

Note that there is no order checking in the range tuples (they are just passed as (low, high) to the corresponding arguments of the underlying code); and that 3-ary methods (custom comparers or message strings) have to be invoked in one or other of these styles to get the association correct

one |->> 0 <| Comparer<int>.Default
(one |->> 0) Comparer<int>.Default
view raw gistfile1.fs hosted with ❤ by GitHub

and F# syntax doesn't permit us to define unary/generic ShouldBeType or ShouldImplement operators in the style of (!-@)<'a>.


No comments :