plugins/nw/skills/nw-pbt-dotnet/SKILL.md
.NET property-based testing with FsCheck, CsCheck, and fsharp-hedgehog frameworks
npx skillsauth add nwave-ai/nwave nw-pbt-dotnetInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Framework | Language | Shrinking | Stateful | Parallel | Choose When | |-----------|----------|-----------|----------|----------|-------------| | FsCheck | C#/F# | Type-based | No | No | F#-first projects, or C# needing mature PBT | | CsCheck | C# | PCG-based | Yes | Yes (linearizability) | C# projects needing concurrent testing | | fsharp-hedgehog | F# | Integrated | No | No | F# projects wanting modern integrated shrinking |
C# default: CsCheck (better shrinking, stateful + parallel). F# default: FsCheck or fsharp-hedgehog.
// C#
using FsCheck; using FsCheck.Xunit;
public class SortProperties
{
[Property]
public bool SortPreservesLength(List<int> xs) =>
xs.OrderBy(x => x).Count() == xs.Count;
}
// F#
open FsCheck
let propSortIdempotent (xs: int list) =
List.sort (List.sort xs) = List.sort xs
Check.Quick propSortIdempotent
Arb.Generate<int>() // any int
Gen.Choose(0, 100) // bounded
Arb.Generate<string>()
Arb.Generate<List<int>>()
// Custom generator
public static Arbitrary<Email> EmailArb() =>
Arb.From(
from name in Arb.Generate<NonEmptyString>()
select new Email($"{name}@example.com")
);
Arb.Register<MyGenerators>(); // register custom arbitraries
Gen.choose (0, 100)
Gen.elements [1; 2; 3]
Gen.oneof [gen1; gen2]
Gen.frequency [(80, gen1); (20, gen2)]
Gen.listOf (Gen.choose (0, 100))
Gen.choose (0, 100) |> Gen.map (fun x -> x * 2)
gen {
let! xs = Gen.listOf (Gen.choose (0, 100))
let! x = Gen.elements xs
return (xs, x)
}
using CsCheck;
[Fact]
public void Sort_Preserves_Length()
{
Gen.Int.Array
.Sample(arr => arr.OrderBy(x => x).Count() == arr.Length);
}
Gen.Int // any int
Gen.Int[0, 100] // bounded
Gen.Double
Gen.String
Gen.Bool
Gen.Int.Array // int[]
Gen.Int.List // List<int>
Gen.Int.HashSet // HashSet<int>
Gen.Int.Select(x => x * 2) // map
Gen.Select(Gen.String, Gen.Int, (name, age) => new User(name, age))
Gen.OneOf(Gen.Int.Select(x => (object)x), Gen.String.Select(x => (object)x))
Gen.Int.Where(x => x > 0) // filter
FsCheck: No stateful testing support. Use CsCheck for stateful and parallel testing. fsharp-hedgehog: No stateful testing support.
[Fact]
public void Store_Matches_Model()
{
Gen.Int.List[0, 100].Sample(operations =>
{
var store = new MyStore();
var model = new Dictionary<string, int>();
// CsCheck uses operation sequences with Check.Sample
});
}
[Fact]
public void Store_Is_Linearizable()
{
Check.SampleConcurrent(
Gen.Operation<MyStore>(/* ... */),
initialState: () => new MyStore()
);
}
Check.SampleConcurrent runs operations sequentially then in parallel, checking against all possible linearizations. Shrinking works for parallel failures -- rare among PBT frameworks.
open Hedgehog
let propReverse = property {
let! xs = Gen.list (Range.linear 0 100) Gen.alpha
return List.rev (List.rev xs) = xs
}
Property.check propReverse
Gen.int (Range.linear 0 100)
Gen.string (Range.linear 0 50) Gen.alpha
Gen.bool
Gen.list (Range.linear 0 50) (Gen.int (Range.linear 0 100))
Gen.option (Gen.int (Range.linear 0 100))
<!-- FsCheck -->
<PackageReference Include="FsCheck.Xunit" Version="3.0.0" />
<!-- CsCheck -->
<PackageReference Include="CsCheck" Version="4.0.0" />
<!-- fsharp-hedgehog -->
<PackageReference Include="Hedgehog" Version="0.13.0" />
<PackageReference Include="Hedgehog.Xunit" Version="0.5.0" />
<!-- All work with xUnit, NUnit, MSTest (CsCheck/FsCheck) -->
Prop.When for preconditions with automatic discard trackingProp.ForAll with Command for lightweight model checkingProp.Collect and Prop.ClassifyCheck.SampleConcurrent -- rare capabilityCheck.Faster for comparative benchmarksCheck.CausalProfilinggen { }, property { } -- most readable F# PBT syntaxtesting
Acceptance test creation methodology for the DISTILL wave. Domain knowledge for the acceptance designer agent: port-to-port principle, prior wave reading, wave-decision reconciliation, graceful degradation, and document back-propagation.
development
Cross-agent collaboration protocols, workflow handoff patterns, and commit message formats for TDD/Mikado/refactoring workflows
development
Creates a phased roadmap.json for a feature goal with acceptance criteria and TDD steps. Use when planning implementation steps before execution.
testing
Acceptance test creation methodology for the DISTILL wave. Domain knowledge for the acceptance designer agent: port-to-port principle, prior wave reading, wave-decision reconciliation, graceful degradation, and document back-propagation.