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
    }
Multiple items
type Provider =
  new : unit -> Provider
  member getSomeData : 'a -> string

Full name: Features.Provider

--------------------
new : unit -> Provider
member Provider.getSomeData : 'a -> string

Full name: Features.Provider.getSomeData
val getProvider : 'a -> Provider

Full name: Features.getProvider
val f : n:int -> int

Full name: Features.f
val n : int
val failwith : message:string -> 'T

Full name: Microsoft.FSharp.Core.Operators.failwith
val tests : obj list

Full name: Features.tests
val typeof<'T> : System.Type

Full name: Microsoft.FSharp.Core.Operators.typeof
type exn = System.Exception

Full name: Microsoft.FSharp.Core.exn
val getSomeData : x:'a -> y:'b -> 'c

Full name: Features.getSomeData
val x : 'a
val y : 'b
val composedTests : obj list

Full name: Features.composedTests
val ( some assertions test ) : obj list

Full name: Features.( some assertions test )
val categorizedTest : obj

Full name: Features.categorizedTest
module Module

from Features
val someTest : obj

Full name: Features.Module.someTest
Fork me on GitHub