NoRedInk/elm-check/1.0.0

NOTE: this package is not compatible with Elm 0.19.1

Check

Property Based Testing module in Elm.

Make a claim

claim : String -> (a -> b) -> (a -> b) -> Investigator a -> Claim

Make a claim about a system.

claim nameOfClaim actualStatement expectedStatement investigator
  1. The nameOfClaim is a string you pass in order to name your claim. This is very useful when trying to debug or reading reports.
  2. The actualStatement is a function which states something about your system. The result of which will be compared by equality == to the result of the expectedStatement.
  3. The expectedStatement is a function which states something which the actualStatement should conform to or be equivalent to. The result of which will be compared by equality == to the result of the actualStatement.
  4. The investigator is an investigator used to generate random values to be passed to the actualStatement and expectedStatement in order to attempt to disprove the claim. If a counter example is found, the investigator will then shrink the counter example until it yields a minimal counter example which is then easy to debug.

Example :

claim_sort_idempotent =
  claim "Sort is idempotent"
    (\list -> List.sort (List.sort (list))
    (\list -> List.sort (list))
    (list int)
claimTrue : String -> (a -> Bool) -> Investigator a -> Claim

Make a claim of truth about a system.

Similar to claim, claimTrue only considers claims which always yield True to be true. If claimTrue manages to find an input which causes the given predicate to yield False, then it will consider that as the counter example.

claimTrue nameOfClaim predicate investigator

Example:

claim_length_list_nonnegative =
  claimTrue "The length of a list is strictly non-negative"
    (\list -> List.length list >= 0)
    (list string)
claimFalse : String -> (a -> Bool) -> Investigator a -> Claim

Make a claim of falsiness about a system.

Analogous to claimTrue, claimFalse only considers claims which always yield False to be true. If claimFalse manages to find an input which causes the given predicate to yield True, then it will consider that as the counter example.

claimFalse nameOfClaim predicate investigator

Example:

claim_length_list_never_negative =
  claimFalse "The length of a list is never negative"
  (\list -> List.length list < 0)
  (list float)

Check a claim

quickCheck : Claim -> Evidence

Quick check a claim.

This function is very useful when checking claims locally. quickCheck will perform 100 checks and use Random.initialSeed 1 as the random seed.

quickCheck claim =
  check claim 100 (Random.initialSeed 1)
check : Claim -> Int -> Seed -> Evidence

Check a claim.

To check a claim, you need to provide the number of checks which check will perform as well a random seed. Given a random seed and a number of checks, check will always yield the same result. Thus, check is especially useful when you wish to reproduce checks.

check claim 100 (Random.initialSeed 1)

Group claims into a suite

suite : String -> List Claim -> Claim

Group a list of claims into a suite. This is very useful in order to group similar claims together.

suite nameOfSuite listOfClaims

Types

type Claim
= Claim String (Int -> Seed -> Evidence)
| Suite String (List Claim)

A Claim is an object that makes a claim of truth about a system. A claim is either a function which yields evidence regarding the claim or a list of such claims.

= Unit UnitEvidence
| Multiple String (List Evidence)

Evidence is the output from checking a claim or multiple claims.

type alias UnitEvidence =
Result FailureOptions SuccessOptions

UnitEvidence is the concrete type returned by checking a single claim. A UnitEvidence can easily be converted to an assertion or can be considered as the result of an assertion.

type alias SuccessOptions =
{ name : String
, seed : Seed
, numberOfChecks : Int
}

SuccessOptions is the concrete type returned in case there is no evidence found disproving a Claim.

SuccessOptions contains:

  1. the name of the claim
  2. the number of checks performed
  3. the seed used in order to reproduce the check.
type alias FailureOptions =
{ name : String
, counterExample : String
, actual : String
, expected : String
, original :
{ counterExample : String
, actual : String
, expected : String
}
, seed : Seed
, numberOfChecks : Int
, numberOfShrinks : Int
}

FailureOptions is the concrete type returned in case evidence was found disproving a Claim.

FailureOptions contains:

  1. the name of the claim
  2. the minimal counterExample which serves as evidence that the claim is false
  3. the value expected to be returned by the claim
  4. the actual value returned by the claim
  5. the seed used in order to reproduce the results
  6. the number of checks performed
  7. the number of shrinking operations performed
  8. the original counterExample, actual, and expected values found prior to performing the shrinking operations.

Multi-arity claims

String
-> (a -> b -> c)
-> (a -> b -> c)
-> Investigator a
-> Investigator b
-> Claim
claim2True : String -> (a -> b -> Bool) -> Investigator a -> Investigator b -> Claim
claim2False : String -> (a -> b -> Bool) -> Investigator a -> Investigator b -> Claim
String
-> (a -> b -> c -> d)
-> (a -> b -> c -> d)
-> Investigator a
-> Investigator b
-> Investigator c
-> Claim
String
-> (a -> b -> c -> Bool)
-> Investigator a
-> Investigator b
-> Investigator c
-> Claim
String
-> (a -> b -> c -> Bool)
-> Investigator a
-> Investigator b
-> Investigator c
-> Claim
String
-> (a -> b -> c -> d -> e)
-> (a -> b -> c -> d -> e)
-> Investigator a
-> Investigator b
-> Investigator c
-> Investigator d
-> Claim
String
-> (a -> b -> c -> d -> Bool)
-> Investigator a
-> Investigator b
-> Investigator c
-> Investigator d
-> Claim
String
-> (a -> b -> c -> d -> Bool)
-> Investigator a
-> Investigator b
-> Investigator c
-> Investigator d
-> Claim
String
-> (a -> b -> c -> d -> e -> f)
-> (a -> b -> c -> d -> e -> f)
-> Investigator a
-> Investigator b
-> Investigator c
-> Investigator d
-> Investigator e
-> Claim
String
-> (a -> b -> c -> d -> e -> Bool)
-> Investigator a
-> Investigator b
-> Investigator c
-> Investigator d
-> Investigator e
-> Claim
String
-> (a -> b -> c -> d -> e -> Bool)
-> Investigator a
-> Investigator b
-> Investigator c
-> Investigator d
-> Investigator e
-> Claim

DSL

elm-check provides a shorthand DSL for authoring claims. The goal of this DSL is to help improve readability and encode intent in the phrasing of your test code.

With the DSL, claims read as either:

  1. claim - (string) - that - (actual) - is - (expected) - for - (investigator)
  2. claim - (string) - true - (predicate) - for - (investigator)
  3. claim - (string) - false - (predicate) - for - (investigator)

Example:

claim_multiplication_by_one_noop =
  claim
    "Multiplying by one does not change a number"
  `true`
    (\n -> n * 1 == n)
  `for`
    int


claim_reverse_append =
  claim
    "Append then reverse is equivalent to reverse then append"
  `that`
    (\(l1, l2) -> List.reverse (l1 ++ l2))
  `is`
    (\(l1, l2) -> List.reverse l1 ++ List.reverse l2)
  `for`
    tuple (list int, list int)

It is important to note that, if you wish to deal with multi-arity functions using this DSL, you must deal explicitly in tuples.

Warning: The DSL follows a very strict format. Deviating from this format will yield potentially unintelligible type errors. While not all of the type errors are strictly necessary, they are there to ensure that the test is authored in a uniform way. As a result, the following functions have horrendous type signatures and you are better off ignoring them.

((a -> b) -> (a -> b) -> Investigator a -> Claim)
-> (a -> b)
-> (a -> b)
-> Investigator a
-> Claim
is : ((a -> b) -> Investigator a -> Claim) -> (a -> b) -> Investigator a -> Claim
for : (Investigator a -> Claim) -> Investigator a -> Claim
((a -> Bool) -> (a -> Bool) -> Investigator a -> Claim)
-> (a -> Bool)
-> Investigator a
-> Claim
((a -> Bool) -> (a -> Bool) -> Investigator a -> Claim)
-> (a -> Bool)
-> Investigator a
-> Claim