harnais_runner v0.1.0 Harnais.Runner.Prova View Source
The prova manages a complete test with one or more test calls.
Each test specification is used to create a prova.
Each test call specification is used to create a cridar.
See Harnais.Runner for the overview.
Prova State
A prova has the following fields:
| Key | Aliases |
|---|---|
:test_flag | :f, :flag |
:test_call | :c, :call |
:test_value | :v, :value |
:test_args | :a, :args |
:test_module | :d, :module |
:test_result | :r, :result |
:test_comp | :comp, :compare, :test_compare |
:test_namer | :n, :namer |
:test_spec | |
:test_cridars |
The default for all fields is the unset value (Plymio.Fontais.Guard.the_unset_value/0).
Prova Field: :test_flag
The :test_flag is optional in the Map and Keyword test forms
but required in the positional Tuple and List forms.
Even when required, usually the :test_flag can be nil; it is ignored.
The :test_flag is most frequently used to catch an exception e.g. {:e ArgumentError}.
The flag is important though when using the reduce runner
(Harnais.Runner.run_tests_reduce_test_value/1) as described above when a
value of :w will set the :test_value for the next test to the
result of the current test.
Prova Field: :test_call
The test call holds one or more (List) call specifications each
of which is used to create a cridar.
Prova Field: :test_value
The :test_value supplies the 2nd argument to the
Harnais.Runner.Cridar.call/2 (the first being the cridar
itself).
Prova Field: :test_args
If the cridar’s :rest_args (sic) is unset, the prova’s
:test_args is used to set the cridar’s :rest_args.
The expectation is that the cridar’s
:rest_args will be used often with the :test_value in a MFA apply.
Prova Field: :test_module
If the cridar’s :test_module is unset, the prova’s :test_module is used.
Prova Field: :test_result
Each prova (test spec) must have a :test_result value.
If the value is a function of arity one, it is called with the actual result.
If the value is a function of arity two, it is called with the actual result and the prova.
The answer from a function is normalised with a definition
of extended truthy: The usual nil, false and true are supplemented
with {:ok, value} (true), {error, error} (false), value is an Exception (false), and value
(true).
Any other value is just compared (==) with the actual result and the compare asserted.
Prova Field: :test_comp
The :test_comp field is used to specify another test that should
return the same answer as the current test.
If the :test_comp is a function, it is treated as the
:test_result.
Otherwise it is treated as a new test specification that is instantiated, tested and compared with the answer from the current test.
See the examples for more specifics.
Prova Field: :test_namer
When the :test_call has an Atom function name (e.g. :get), the namer
function is called with the function name and should return the
actual function (Atom) to call in the :test_module.
Prova Field: :test_spec
The original (pre transform) test specification.
Prova Field: :test_cridars
Each test call specification in the :test_call is used to create a
cridar. The :test_cridars holds the list
of cridars.
Link to this section Summary
Link to this section Types
Link to this section Functions
new/1 creates a new instance of the module’s struct and, if the optional
opts were given, calls update/2 with the instance and the opts,
returning {:ok, instance}, else {:error, error}.
test/1 takes a prova and runs each test call (cridar)
Examples
A simple example of a Map.get/2 call where the result is expected to be 42:
iex> {:ok, {answer, %PROVA{}}} = [
...> module: Map, value: %{a: 42},
...> args: [:a], call: [:get], result: 42,
...> ] |> PROVA.new! |> PROVA.test
...> answer
42
The :test_result here is 99 so an error is returned:
iex> {:error, error} = [
...> module: Map, value: %{a: 42},
...> args: [:a], call: [:get], result: 99,
...> ] |> PROVA.new! |> PROVA.test
...> error |> Exception.message
"result mismatch; expect 99; actual 42"
Here the :test_result is a fun/1 that always returns false causing an error to be returned.
iex> {:error, error} = [
...> module: Map, value: %{a: 42},
...> args: [:a], call: [:get], result: fn _ -> false end,
...> ] |> PROVA.new! |> PROVA.test
...> error |> Exception.message
"result invalid, got: 42"
Precreated cridars can be used in the :test_call:
iex> {:ok, %CRIDAR{} = cridar} = [
...> mod: Map, fun: :get, args: [%{a: 42}, :a]
...> ] |> CRIDAR.new
...> {:ok, {answer, %PROVA{}}} = [
...> module: List, value: %{b: 99},
...> args: [:a], call: cridar, result: 42,
...> ] |> PROVA.new! |> PROVA.test
...> answer
42
In this example two precreated cridars are chained together: the result
of the first is used as the :test_value for the second. Note the
cridars have :rest_args (not :args) as they will be applied
together with the :test_value:
iex> call1 = [f: :delete, rest_args: :a] |> CRIDAR.new!
...> call2 = [f: :get, rest_args: [:a, :no_a_found]] |> CRIDAR.new!
...> {:ok, {answer, %PROVA{}}} = [
...> mod: Map, call: [call1, call2],
...> value: %{a: 1}, result: :no_a_found,
...> ] |> PROVA.new! |> PROVA.test
...> answer
:no_a_found
Test call specifications can be mixed: A variation of the example
above where a value is put for :a using an explicit fun:
iex> call1 = [f: :delete, rest_args: :a] |> CRIDAR.new!
...> call2 = [f: :get, rest_args: [:a, :no_a_found]] |> CRIDAR.new!
...> call3 = fn test_value -> Map.put(test_value, :a, 42) end
...> {:ok, {answer, %PROVA{}}} = [
...> mod: Map, call: [call1, call3, call2],
...> value: %{a: 1}, result: 42,
...> ] |> PROVA.new! |> PROVA.test
...> answer
42
Same example but the put for :a is Keyword format call specification:
iex> call1 = [f: :delete, rest_args: :a] |> CRIDAR.new!
...> call2 = [f: :get, rest_args: [:a, :no_a_found]] |> CRIDAR.new!
...> call3 = [f: :put, rest_args: [:a, 99]]
...> {:ok, {answer, %PROVA{}}} = [
...> mod: Map, call: [call1, call3, call2],
...> value: %{a: 1}, result: 99,
...> ] |> PROVA.new! |> PROVA.test
...> answer
99
These two examples show the use of the :test_namer. Note it is applied to precreated cridars as well.
iex> {:ok, {answer, %PROVA{}}} = [
...> test_namer: fn :g -> :get end,
...> module: Map, value: %{a: 42},
...> args: [:a], call: [:g], result: 42,
...> ] |> PROVA.new! |> PROVA.test
...> answer
42
iex> call1 = [f: :d, rest_args: :a] |> CRIDAR.new!
...> call3 = [f: :p, rest_args: [:a, 99]]
...> call2 = [f: :g, rest_args: [:a, :no_a_found]] |> CRIDAR.new!
...> test_namer = fn
...> :g -> :get
...> :p -> :put
...> :d -> :delete
...> end
...> {:ok, {answer, %PROVA{}}} = [
...> namer: test_namer,
...> mod: Map, call: [call1, call3, call2],
...> value: %{a: 1}, result: 99,
...> ] |> PROVA.new! |> PROVA.test
...> answer
99
The new few examples demonstrate the use of :test_comp. First, if
the :test_comp is a function, it is treated as a :test_result
function. Here is always returns true.
iex> {:ok, {answer, %PROVA{}}} = [
...> compare: fn _ -> true end,
...> module: Map, value: %{a: 42},
...> args: [:a], call: [:get],
...> ] |> PROVA.new! |> PROVA.test
...> answer
42
Otherwise the :test_comp is used to create a compare prova.
which is then, in turn, used to create a :test_result arity 2 function for the test prova.
When the test prova’s :test_result fun/2 function is called, the first
argument (the test answer) is used to set the compare prova’s :test_result value
(but only if not already set).
Similarly the :test_value from the second argument (the test prova)
is used to set the compare prova’s :test_value (but
again only if not already set).
The compare prova is then tested and if it returns true according to the extended truthy definition,
{:ok, {test_answer, test_prova}} is returned.
iex> {:ok, {answer, %PROVA{}}} = [
...> compare: [call: fn test_value -> test_value |> Map.get(:a) end],
...> module: Map, value: %{a: 42},
...> args: [:a], call: [:get],
...> ] |> PROVA.new! |> PROVA.test
...> answer
42
iex> {:error, error} = [
...> compare: [call: fn test_value -> test_value |> Map.get(:b) end],
...> module: Map, value: %{a: 42},
...> args: [:a], call: [:get],
...> ] |> PROVA.new! |> PROVA.test
...> error |> Exception.message
"result invalid, got: 42"
update/2 takes an instance of the module’s struct and an optional opts.
The opts are normalised by calling the module’s update_canonical_opts/1
and then reduced with update_field/2:
opts |> Enum.reduce(instance, fn {k,v}, s -> s |> update_field({k,v}) end)
{:ok, instance} is returned.