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.