View Source TypeCheck.ExUnit (TypeCheck v0.13.7)

Provides macros for 'spectests': spec-automated property-testing.

The core macro exposed by this module is spectest/2, which will will check for all function-specs in the given module, whether those functions correctly follow the spec.

To use this functionality, add use TypeCheck.ExUnit to your testing module.

Currently, spectesting uses StreamData under the hood. This means that to use the spectest functionality, you need to add the :stream_data dependency to your application (it is an optional dependency of TypeCheck.)

In the future, support for other property-generating libraries might be added.

What is a spectest?

A 'function-specification test' is a property-based test in which we check whether the function adheres to its invariants (also known as the function's contract or preconditions and postconditions).

We generate a large amount of possible function inputs, and for each of these, check whether the function:

  • Does not raise an exception.
  • Returns a result that type-checks against the spec's return-type. (To be precise, if an incorrect result is returned, the function is wrapped in will end up raising an exception for this.)

While @spec!s themselves ensure that callers do not mis-use your function, a spectest ensures¹ that the function itself is working correctly.

Spectests are given its own test-category in ExUnit, for easier recognition (Just like 'doctests' and 'properties' are different from normal tests, so are 'spectests'.)

¹: Because of the nature of property-based testing, we can never know for 100% sure that a function is correct. However, with every new randomly-generated test-case, the level of confidence grows a little. So while we can never by fully sure, we are able to get asymptotically close to it.

Summary

Functions

Sets up a testing module for spectesting.

Tests the functions in module against their @spec!s.

Functions

Link to this macro

__using__(opts)

View Source (macro)

Sets up a testing module for spectesting.

Not normally invoked directly, but rather by calling use TypeCheck.ExUnit.

Currently does not accept any options, but this might change in the future.

Link to this macro

spectest(module, options \\ [])

View Source (macro)

Tests the functions in module against their @spec!s.

spectest will look at all functions which have a TypeCheck spec in module, and will for each of them run a 'spectest'. See the module documentation for more information on spectests in general.

Examples

defmodule MyModuleTest do
  use ExUnit.Case, async: true
  use TypeCheck.ExUnit

  # Test all functions that have `@spec!`s in `MyModule`
  spectest MyModule

  # Test all functions that have `@spec!`s in `MyOtherModule`,
  # except `MyOtherModule.bar/2` and `MyOtherModule.baz/0`
  spectest MyOtherModule, except: [{:bar, 2}, {:baz, 0}]

  # Test only `OneMoreModule.foo/2` and `MyOtherModule.qux/0`
  spectest OneMoreModule, only: [{:foo, 2}, {:qux, 0}]
end

Options

  • :except
  • :only
  • :initial_seed
  • :generator

Except and Only

By default, all functions in the module (that have an associated @spec!) will be tested.

If any of them need to be skipped, you can add them as a list of {name, arity}-pairs under except:.

If instead of excluding a few functions, you want to only test a small subset of functions, you can add them as {name, arity}-pairs under only:.

Initial seed

The :initial_seed-option can be used to seed the property-generation. It expects an integer value. This option is passed on to the generator automatically (without requiring the usage of generator-specific options). By default, the seed of the ExUnit configuration is used (which by default differs every test run).

Generator

The generator: option expects either the name of a property-testing library, or a {name, options}-tuple. (If only the name is specified, this is a shorthand for {Name, []}).

For now, only StreamData is supported, and this is its default value. If you want to pass extra options to the library, the notation {StreamData, list_of_options} can be passed. For the list of options supported by StreamData, see StreamData.check_all/3.