CtrlK
BlogDocsLog inGet started
Tessl Logo

evilissimo/property-based-testing

Generates **property-based tests** that use randomized input generation to validate invariants and contracts (rather than hand-picked examples). Triggers when the conversation involves: PBT frameworks (Hypothesis library for Python, fast-check for TypeScript, proptest for Rust, rapid for Go, RapidCheck for C++); concepts like invariants, contracts, round-trip symmetry, encode/decode, serialize/deserialize, generative testing, or shrinking; or requests to find edge cases that example-based tests miss — e.g., "find edge cases automatically", "test all possible inputs", "verify this property holds". Does NOT trigger for: writing regular example-based unit tests, debugging, CI/CD setup, UI/component testing, or integration/E2E testing. Identifies up to 7 property patterns (round-trip, idempotence, invariance, metamorphic, inverse, ordering, no-crash), designs input generators, writes property tests, and extracts regression tests from failures.

91

1.11x
Quality

90%

Does it follow best practices?

Impact

94%

1.11x

Average score across 5 eval scenarios

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

golang.mdreferences/

Go: rapid

Dependency: go get github.com/flyingmutant/rapid

Core API

import "github.com/flyingmutant/rapid"

Common Generators

rapid.Int()                           // Any int
rapid.IntRange(0, 100)                // Constrained int
rapid.Float64()                       // Any float64
rapid.Float64Range(0, 1)             // Constrained float
rapid.String()                        // Any string (including empty)
rapid.StringN(1, 50)                 // Sized string (1-50 chars)
rapid.Bool()                          // true or false
rapid.Byte()                          // Any byte
rapid.Slice(rapid.Int())              // Slice of ints
rapid.SliceN(rapid.Int(), 1, 10)     // Sized slice
rapid.Map(rapid.String(), rapid.Int())  // map[string]int
rapid.Custom(genFn)                   // Custom generator

Writing Property Tests (rapid.Check)

Test functions must take *rapid.T and use its Make method:

func TestSortLengthInvariant(t *testing.T) {
    rapid.Check(t, func(t *rapid.T) {
        lst := rapid.Slice(rapid.Int()).Draw(t, "lst")
        originalLen := len(lst)
        sort.Ints(lst)
        if len(lst) != originalLen {
            t.Fatalf("sort changed length: got %d, want %d", len(lst), originalLen)
        }
    })
}

func TestSortOrdered(t *testing.T) {
    rapid.Check(t, func(t *rapid.T) {
        lst := rapid.Slice(rapid.Int()).Draw(t, "lst")
        sort.Ints(lst)
        for i := 0; i < len(lst)-1; i++ {
            if lst[i] > lst[i+1] {
                t.Fatalf("sort failed at index %d: %d > %d", i, lst[i], lst[i+1])
            }
        }
    })
}

Preconditions (Filtering)

rapid.Check(t, func(t *rapid.T) {
    a := rapid.Int().Draw(t, "a")
    b := rapid.Int().Filter(func(i int) bool { return i != 0 }).Draw(t, "b")
    // Now b is never 0
    result := a / b * b  // Lossy for integers, but division by zero is avoided
    _ = result
})

Custom Generators

type User struct {
    Name string
    Age  int
}

var userGen = rapid.Custom(func(t *rapid.T) User {
    return User{
        Name: rapid.StringN(1, 50).Draw(t, "name"),
        Age:  rapid.IntRange(0, 150).Draw(t, "age"),
    }
})

func TestUserProperties(t *testing.T) {
    rapid.Check(t, func(t *rapid.T) {
        user := userGen.Draw(t, "user")
        if len(user.Name) == 0 {
            t.Error("expected non-empty name")
        }
        if user.Age < 0 || user.Age > 150 {
            t.Errorf("age out of range: %d", user.Age)
        }
    })
}

Shrinking

rapid automatically shrinks failing cases to minimal examples. The failure output shows the shrunken input:

test.go:42: after 37 attempts, counterexample found:
test.go:42:  lst = [3, 1, 2]

Configuration

rapid.Check(t, func(t *rapid.T) {
    // ...
})
// Default: 100 iterations
// Set via: rapid.Check(t, func(...) { ... }, rapid.MaxExamples(500))

Note: rapid doesn't have a built-in way to configure iterations per test call in the current stable API. Use environment awareness: run fewer iterations in CI, more locally.

Running

go test -v ./...                      # All tests
go test -v -run TestSort -count=1    # Specific test, no cache
go test -v -run "TestSort" -count=5  # Run multiple times for flakiness detection

SKILL.md

tessl.json

tile.json