Persimmon


Features

Using Computation Expressions

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())
  }
]

Mark a test

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.

Composable test

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
  }
]

Continuable assertion

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
  }
]

Categorize tests

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>
Fork me on GitHub