Repository F# setup
open Reified
open Reified.Refinements
open Reified.Result
open Reified.Schema.JsonBuild variations and matrices
Keep one representative baseline and describe each test case as a strict immutable edit.
The four functions on this page are variant, variants, dimension, and matrix.
open Reified
open Reified.DataDSL
let baseline =
data [
"name" => "Ada"
"plan" => "free"
"region" => "au"
"roles" => [ "author" ]
]
ReifiedReified.DataDSLConcise opt-in syntax for literals, immutable edits, cases, and matching.
baseline: Datadata: DataField list -> DataBuilds an object from ordered field instructions. data [ "name" => "Ada"; "active" => true ] // Data.Object [ "name", Data.Text "Ada"; "active", Data.Bool true ]
(=>): string -> ^value -> DataFieldAssociates a field name with an exact value or recursive data pattern.
The three types
Case generation moves through three small record types.
type DataVariation = { Name: string; Edits: DataEdit list }
type DataDimension = { Name: string; Variations: DataVariation list }
type DataCase = { Name: string; Value: Data }
07-data_31-how-to-build-test-cases.md_page.DataVariationName: stringstringAn abbreviation for the CLI type . Basic Types
Edits: DataEdit listReified.DataEditAn opaque immutable edit applied by Data.tryPatch or Data.patch.
listThe type of immutable singly-linked lists. See the module for further operations related to lists. Use the constructors [] and :: (infix) to create values of this type, or the notation [1; 2; 3]. Use the values in the List module to manipulate values of this type, or pattern match against the values directly. See also F# Language Guide - Lists.
07-data_31-how-to-build-test-cases.md_page.DataDimensionVariations: DataVariation list07-data_31-how-to-build-test-cases.md_page.DataCaseValue: DataReified.DataA portable tree for structured data. Data preserves null, text, number, Boolean, list, and object distinctions without depending on a serializer or input format. Data is a structured-value model, not a source syntax tree. It does not model whitespace, comments, source locations, or other format-specific syntax. Number values currently retain a lexical token so adapters do not narrow arbitrary-size integers, decimal precision, or exponent notation to one runtime numeric type.
variant builds a DataVariation: a name and the edits, with no baseline attached yet. It is a description, so the
same variation list can be applied to more than one baseline. dimension groups variations into one independent axis
of a matrix.
variants and matrix apply those descriptions to a baseline and return DataCase list — each case is a name and
the value that resulted. A DataCase is what a test iterates over.
Why not just Data list
The name. A bare list of values loses which case a value came from, so a failure reports index 5 instead of
plan: pro / region: US / roles: admin, and reordering the list silently renumbers every test. Carrying the name with
the value means:
- Test-framework case names and assertion messages come straight from
case.Name. matrixcomposes names from its dimensions, so a case identifies itself by the choice made on every axis rather than by a position in a Cartesian product.variantscan reject duplicate names, which catches two cases that were meant to differ but describe the same thing.
The value is a plain Data, so anything on the rest of these pages — Data.patch, matching, Data.compare,
Data.Json.render — applies to case.Value directly.
for case in cases do
test case.Name (fun () -> case.Value |> submit |> matching [ at "status" "rejected" ])Build independent cases
let cases =
baseline
|> variants [
variant "valid" []
variant "missing name" [ remove "name" ]
variant "blank name" [ replace "name" "" ]
variant "wrong name shape" [ replace "name" [ "Ada" ] ]
]
cases: DataCase listbaseline: Data(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
variants: DataVariation list -> Data -> DataCase listMaterializes named variations from one baseline. variants [ variant "inactive" [ replace "active" false ] ] (data [ "active" => true ]) // [ { Name = "inactive"; Value = data [ "active" => false ] } ]
variant: string -> DataEdit list -> DataVariationDeclares one named variation from a baseline.
remove: string -> DataEditRemoves an existing field or list item.
replace: string -> ^a -> DataEditReplaces an existing value.
variants rejects duplicate names and preserves declaration order.
The result has four DataCase values in the shown order:
valid name = "Ada"
missing name name is absent
blank name name = ""
wrong name shape name = ["Ada"]Build a Cartesian matrix
let matrixCases =
baseline
|> matrix [
dimension "plan" [
variant "free" []
variant "pro" [ replace "plan" "pro" ]
]
dimension "region" [
variant "AU" []
variant "US" [ replace "region" "us" ]
]
dimension "roles" [
variant "none" [ replace "roles" ([]: string list) ]
variant "admin" [ replace "roles" [ "admin" ] ]
]
]
matrixCases: DataCase listbaseline: Data(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
matrix: DataDimension list -> Data -> DataCase listMaterializes a deterministic Cartesian matrix, limited to 256 cases. open Reified.DataDSL let baseline = data [ "active" => true ] matrix [ dimension "status" [ variant "active" []; variant "inactive" [ replace "active" false ] ] ] baseline // cases named "status: active" and "status: inactive"
dimension: string -> DataVariation list -> DataDimensionDeclares one named dimension in a Cartesian matrix.
variant: string -> DataEdit list -> DataVariationDeclares one named variation from a baseline.
replace: string -> ^a -> DataEditReplaces an existing value.
stringAn abbreviation for the CLI type . Basic Types
listThe type of immutable singly-linked lists. See the module for further operations related to lists. Use the constructors [] and :: (infix) to create values of this type, or the notation [1; 2; 3]. Use the values in the List module to manipulate values of this type, or pattern match against the values directly. See also F# Language Guide - Lists.
Names follow dimension order, such as plan: pro / region: AU / roles: admin.
The initial matrix limit is 256 combinations. The product is checked before cases are materialized.
This matrix produces eight cases. The first is
plan: free / region: AU / roles: none; its value has plan = "free", region = "au", and roles = [].
The last is plan: pro / region: US / roles: admin; its value has plan = "pro", region = "us", and
roles = ["admin"].
cases |> List.map _.Name
// =>
// [ "plan: free / region: AU / roles: none"
// "plan: free / region: AU / roles: admin"
// "plan: free / region: US / roles: none"
// "plan: free / region: US / roles: admin"
// "plan: pro / region: AU / roles: none"
// "plan: pro / region: AU / roles: admin"
// "plan: pro / region: US / roles: none"
// "plan: pro / region: US / roles: admin" ]
cases: DataCase list(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Microsoft.FSharp.Collections.ListModuleContains operations for working with values of type . Operations for collections such as lists, arrays, sets, maps and sequences. See also F# Collection Types in the F# Language Guide.
map: ('T -> 'U) -> 'T list -> 'U listBuilds a new collection whose elements are the results of applying the given function to each of the elements of the collection. The function to transform elements from the input list. The input list. The list of transformed elements. let inputs = [ "a"; "bbb"; "cc" ] inputs |> List.map (fun x -> x.Length) Evaluates to [ 1; 3; 2 ]
_arg1: DataCaseName: stringThe deterministic case name.
Inspect dynamic patch failures
match Data.tryPatch edits baseline with
| Ok value -> runCase value
| Error failures ->
failures
|> List.iter (fun failure ->
printfn "Edit %d at %s: %s" failure.EditIndex failure.Path failure.Message)Data.patch raises DataPatchException with the same failures.
For append "name" "Grace", the result is one failure at edit index 0, path name, with a message stating that a
list was expected but text was found. The baseline remains unchanged.
Data.tryPatch [ append "name" "Grace" ] baseline
// => Error [
// { EditIndex = 0
// Path = "name"
// Message = "Expected a list but found text." }
// ]
Reified.DataModuletryPatch: DataEdit list -> Data -> Result<Data,DataPatchFailure list>Applies immutable edits atomically in declaration order. Data.tryPatch [ replace "name" "Grace" ] (data [ "name" => "Ada" ]) // Ok (data [ "name" => "Grace" ])
append: string -> ^a -> DataEditAppends an item to an existing list.
baseline: Data