Skip to content
Development
Skill

/nw-pbt-erlang-elixir

Erlang/Elixir property-based testing with PropEr, PropCheck, and StreamData frameworks

From plugin
nwave
635183 skills37 agents

Claude Code

$ npx -y skills add nWave-ai/nWave --skill nw-pbt-erlang-elixir --agent claude-code

SKILL.md

nw-pbt-erlang-elixir.SKILL.md
name: nw-pbt-erlang-elixir
agent: nw-functional-software-crafter
description: Erlang/Elixir property-based testing with PropEr, PropCheck, and StreamData frameworks
user-invocable: false

PBT Erlang/Elixir -- PropEr, PropCheck, StreamData

Framework Selection

| Framework | Language | Stateful | Parallel | Choose When | |-----------|----------|----------|----------|-------------| | PropEr | Erlang | Yes | Yes (linearizability) | Erlang projects needing full stateful/parallel testing | | PropCheck | Elixir | Yes (via PropEr) | Yes (via PropEr) | Elixir projects needing stateful/parallel testing | | StreamData | Elixir | No | No | Elixir projects needing only stateless PBT |

PropCheck wraps PropEr with Elixir syntax. StreamData is pure Elixir but lacks stateful testing.

Quick Start

%% PropEr (Erlang)
-include_lib("proper/include/proper.hrl").

prop_sort_preserves_length() ->
    ?FORALL(List, list(integer()),
        length(lists:sort(List)) =:= length(List)).

%% Run: proper:quickcheck(my_module:prop_sort_preserves_length()).

Syntax Differences

| Concept | PropEr (Erlang) | PropCheck (Elixir) | StreamData (Elixir) | |---------|-----------------|--------------------|--------------------| | Import | `-include_lib("proper/include/proper.hrl").` | `use PropCheck` | `use ExUnitProperties` | | Property | `?FORALL(X, gen(), body)` | `forall x <- gen() do body end` | `check all x <- gen() do assert body end` | | Integer | `integer()` | `integer()` | `integer()` | | List | `list(integer())` | `list(integer())` | `list_of(integer())` | | Run | `proper:quickcheck(prop())` | `mix test` | `mix test` |

Generator Cheat Sheet (PropEr)

Primitives

integer()                  % any integer
integer(0, 100)            % bounded
float()
binary()                   % binary data
boolean()
atom()

Collections

list(integer())            % list of integers
non_empty(list(integer())) % non-empty list
vector(5, integer())       % fixed-length list
{integer(), binary()}      % tuple (direct syntax)

Combinators

oneof([integer(), binary()])      % union
elements([a, b, c])               % pick from list
frequency([{80, integer()}, {20, atom()}])  % weighted

%% ?LET (map/transform)
even() ->
    ?LET(N, integer(), N * 2).

%% ?SUCHTHAT (filter -- use sparingly)
non_empty_list() ->
    ?SUCHTHAT(L, list(integer()), L =/= []).

%% Nested ?LET (dependent generation)
list_and_element() ->
    ?LET(List, non_empty(list(integer())),
        ?LET(Elem, elements(List),
            {List, Elem})).

Recursive

tree(Type) ->
    ?SIZED(Size, tree(Size, Type)).
tree(0, Type) -> {leaf, Type};
tree(Size, Type) ->
    frequency([
        {1, {leaf, Type}},
        {5, ?LAZY({node, Type, tree(Size div 2, Type), tree(Size div 2, Type)})}
    ]).

Shrinking

year() ->
    ?SHRINK(integer(0, 9999), [integer(1970, 2000)]).

date() ->
    ?LETSHRINK([Y, M, D],
               [integer(1, 9999), integer(1, 12), integer(1, 31)],
               {Y, M, D}).

Stateful Testing (proper_statem)

-behaviour(proper_statem).

initial_state() -> #{items => #{}}.

command(#{items := Items}) ->
    oneof([
        {call, ?MODULE, put, [key(), value()]},
        {call, ?MODULE, get, [elements(maps:keys(Items))]}
            || maps:size(Items) > 0
    ]).

precondition(#{items := Items}, {call, _, get, [Key]}) ->
    maps:is_key(Key, Items);
precondition(_, _) -> true.

postcondition(#{items := Items}, {call, _, get, [Key]}, Result) ->
    Result =:= maps:get(Key, Items);
postcondition(_, _, _) -> true.

next_state(State = #{items := Items}, _Var, {call, _, put, [Key, Val]}) ->
    State#{items := Items#{Key => Val}};
next_state(State, _, _) -> State.

prop_store() ->
    ?FORALL(Cmds, commands(?MODULE),
        begin
            {History, State, Result} = run_commands(?MODULE, Cmds),
            cleanup(),
            Result =:= ok
        end).

StreamData: No stateful testing. PropCheck: Same proper_statem callbacks with Elixir syntax.

Parallel Testing (Linearizability)

prop_store_parallel() ->
    ?FORALL(Cmds, parallel_commands(?MODULE),
        begin
            {Sequential, Parallel, Result} = run_parallel_commands(?MODULE, Cmds),
            cleanup(),
            Result =:= ok
        end).

Swap `commands` for `parallel_commands`, `run_commands` for `run_parallel_commands`. Framework generates sequential prefix + parallel branches and checks all linearizations.

Test Runner Integration

%% PropEr: {deps, [{proper, "1.4.0"}]}. Run: rebar3 proper
%% PropCheck: {:propcheck, "~> 1.4", only: :test}. Run: mix test
%% StreamData: {:stream_data, "~> 1.0", only: :test}. Run: mix test

Unique Features

  • **proper_fsm**: Finite state machine testing module (distinct from generic state machines)
  • **PULSE integration**: Controlled scheduling for parallel test race detection
  • **Symbolic references**: First-class support with two-phase (abstract + concrete) execution
  • **collect/aggregate**: Built-in distribution analysis for generator quality verification
  • **Linearizability checking**: One of the most mature implementations in any PBT framework
Read more
@nwave-ai
Ships withnwave

nWave supplies specialist roles and a host-led delivery workflow for software teams.

635183 skills37 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
635
Star history of nWave-ai/nWave on GitHub, a chart by star-history.com

Other skills on nwave.

nw-ab-agent-template

nw-ab-agent-template

KNOWLEDGE — the canonical agent-spec template (frontmatter + body skeleton). Reference loaded by the create/migrate procedures;…

@nwave-ai@nwave-aiInstall Skill
nw-ab-anti-patterns

nw-ab-anti-patterns

KNOWLEDGE — agent/skill/command anti-pattern catalog with fixes. Reference scanned by validate-spec; no sequence.

@nwave-ai@nwave-aiInstall Skill
nw-ab-create-agent

nw-ab-create-agent

PROCEDURE — create a NEW agent via the 5-phase workflow (ANALYZE→DESIGN→CREATE→VALIDATE→REFINE). Trigger: build a new AI agent.…

@nwave-ai@nwave-aiInstall Skill
nw-ab-examples

nw-ab-examples

KNOWLEDGE — canonical worked examples for agent creation, migration, and command optimization. Reference for the relevant…

@nwave-ai@nwave-aiInstall Skill
nw-ab-house-style

nw-ab-house-style

KNOWLEDGE — caveman-native authoring house style + by-construction guarantees (Reasoning Mandate injection, measured-gain…

@nwave-ai@nwave-aiInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills13d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react4d252k
@facebook@facebookInstall Skill

Questions about nw-pbt-erlang-elixir.

Which coding agents can use nw-pbt-erlang-elixir?
nw-pbt-erlang-elixir installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.