Repository F# setup
open Reified
open Reified.Refinements
open Reified.ResultBuild variations and matrices
Keep one representative baseline and describe each test case as a strict immutable edit.
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.
Requires & types
Requires: open Reified.DataDSL (packages: Reified.Data)val baseline: DataThe three types
type DataVariation = { Name: string; Edits: DataEdit list }
type DataDimension = { Name: string; Variations: DataVariation list }
type DataCase = { Name: string; Value: Data }
FsLiveDocsGeneratedPage7_9A5DDEA924C8.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.
FsLiveDocsGeneratedPage7_9A5DDEA924C8.DataDimensionVariations: DataVariation listFsLiveDocsGeneratedPage7_9A5DDEA924C8.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.
Requires & types
Requires: open Reified (packages: Reified.Data)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 a 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
A bare list of values loses case names, 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.
case.Value is plain Data, so Data.patch, matching, Data.compare, and Data.Json.render apply 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.
Requires & types
Requires: open Reified.DataDSL (packages: Reified.Data)val cases: DataCase listvariants 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.
Requires & types
Requires: open Reified.DataDSL (packages: Reified.Data)val matrixCases: DataCase listNames follow dimension order, such as plan: pro / region: AU / roles: admin.
The matrix limit is 256 combinations. The product is checked before cases are materialized. Exceeding it raises
System.ArgumentException with the message "The matrix would create N cases; the maximum is 256.", before any
case is built. A dimension with no variations does not throw: its product is zero, so matrix returns an empty case
list instead. When an empty dimension is itself a test-authoring bug, check dimension.Variations before calling
matrix.
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"].
matrixCases |> 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" ]
matrixCases: 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.
Requires & types
Requires: open Reified (packages: Reified.Data)Inspect patch failures from an edit list
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: DataRequires & types
Requires: open Reified; open Reified.DataDSL (packages: Reified.Data)