In F#, I want to build a hierarchical data structure in a way with a minimum
amount of language noise. The actual problem is trying to build an RSpec
inspired framework using F#. RSpec allows the construction of tests in a nested way. For example.
describe Order do
context "with no items" do
it "behaves one way" do
# ...
end
end
context "with one item" do
it "behaves another way" do
# ...
end
end
end
I have something working here, but there are some
potential problems with the current solution. In particular general
setup/teardown code can become somewhat ugly.
My primary concern is to write an API that allows the user to write
tests/specifications in a way, where the language gets as little in the way as
possible.
I think that using computation expressions could allow my to create a better
API, but I am struggling with implementing nested contexts.
Currently I do have a builder that allows me to write:
let specs =
describe "Some feature" {
it "has some behavior" (fun () ->
// Test code goes here
)
}
But in order to have nested contexts I need to be able to write something like
this:
let specs =
describe "Some feature" {
describe "in some context" {
it "has some behavior" (fun () ->
// Test code goes here
)
}
describe "in some other context" {
it "has some behavior" (fun () ->
// Test code goes here
)
}
}
I have no idea how to implement the nested context. Or even if it is possible to
bend F# in a way to allow me to create such a builder.
I did make an experiment, that allowed me to write this:
let specs =
desribe "Some feature" {
child (describe "in some other" {
it "has some behavior" (fun () ->
// Test code goes here
)
})
child (describe "in some context" {
it "has some behavior" (fun () ->
// Test code goes here
)
})
}
But the added parenthesis and explicit builder construction is exactly what I
want to avoid in the first place.
type DescribeBuilder(name : string) =
[<CustomOperation("it")>]
member __.It (x, name, test : unit -> unit) =
let t = TestContext.createTest name test
x |> TestContext.addTest t
[<CustomOperation("child")>]
member __.Child(x, child :TestContext.T) =
x |> TestContext.addChildContext child
member __.Yield y =
TestContext.create name
member __.Delay (x) =
x()
let describe name = new DescribeBuilder(name)
Best Answer
Have you tried any of the bang (
!
) keywords? You might be able to do it like so:assuming your
it
returns something in thedescribe
workflow.(The above might be improper F# as I've been away from it for a while, but you should be able to do something like it.)