Persimmon uses Computation Expressions to:
- write a test
- write a parameterized test
- specify the expected exception
By using Computation Expressions, we can write the tests more concisely, flexibly, and safely than the other existing testing frameworks for F#.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
|
let f n =
if n > 0 then n * 2
else failwith "oops!"
let tests = [
test "(f 21) should be equal to 42" {
do! assertEquals 42 (f 21)
}
test "(f 0) should raise exn" {
let! e = trap { it (f 0) }
do! assertEquals (typeof<exn>) (e.GetType())
}
]
|
In many testing frameworks, they mark tests with attributes or by their naming rules.
However, Persimmon doesn't use them to mark the tests.
Persimmon marks variables, properties, and methods (only unit
argument) whose return types are TestObject
or its subtypes.
A test of Persimmon is composable.
It can accept several tests and return a new test.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
|
let getSomeData x y = test "" {
let provider = getProvider x
let res = provider.getSomeData y
do! assertNotEquals "" res
return res
}
let composedTests = [
test "test1" {
let! data = getSomeData [(10, "ten"); (3, "three")] 3
do! assertEquals "three" data
}
test "test2" {
let! data = getSomeData [(10, "ten"); (3, "three")] 10
do! assertEquals "ten" data
}
]
|
An assetion of Persimmon (doesn't have a result) continues to execute remaining assertions even if it violated.
And Persimmon can enumerate all violated assertions in the test.
This behaviour brings the advantage that a test doesn't sacrifice the amount of information and can be simplified to write it.
1:
2:
3:
4:
5:
6:
7:
8:
|
// This test reports two assertion violations.
let ``some assertions test`` = [
test "test1" {
do! assertEquals 1 2 // execute and violate
do! assertEquals 2 2 // continue to execute and pass
do! assertEquals 3 4 // continue to execute and violate
}
]
|
By setting categories for test, you can filter tests to be run by category.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
|
let categorizedTest =
test "test1" {
do! assertEquals 2 1
}
|> category "SomeCategory" // categorize as "SomeCategory"
// categorize all tests in module as "SomeCategory"
[<Category("SomeCategory")>]
module Module =
let someTest =
test "test2" {
do! assertEquals 2 1
}
|
namespace Persimmon
Multiple items
type Provider =
new : unit -> Provider
member getSomeData : 'a -> string
--------------------
new : unit -> Provider
val getProvider : 'a -> Provider
val f : n:int -> int
val n : int
val failwith : message:string -> 'T
val tests : TestCase<unit> list
val test : name:string -> TestBuilder
val assertEquals : expected:'a -> actual:'a -> AssertionResult<unit> (requires equality)
val e : exn
val trap : TrapBuilder
custom operation: it ('b)
Calls TrapBuilder.It
val typeof<'T> : System.Type
type exn = System.Exception
System.Exception.GetType() : System.Type
val getSomeData : x:'a -> y:'b -> TestCase<string>
val x : 'a
val y : 'b
val provider : Provider
val res : string
member Provider.getSomeData : 'a -> string
val assertNotEquals : notExpected:'c -> actual:'c -> AssertionResult<unit> (requires equality)
val composedTests : TestCase<unit> list
val data : string
val ( some assertions test ) : TestCase<unit> list
val categorizedTest : TestCase<unit>
val category : category:string -> target:TestCase<'T> -> TestCase<'T>
Multiple items
type CategoryAttribute =
inherit Attribute
new : [<ParamArray>] categories:string [] -> CategoryAttribute
member Categories : string []
--------------------
new : [<System.ParamArray>] categories:string [] -> CategoryAttribute
module Module
from Features
val someTest : TestCase<unit>