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

cpp.mdreferences/

C++: RapidCheck

Installation:

  • vcpkg: vcpkg install rapidcheck
  • CMake FetchContent: See github.com/emil-e/rapidcheck

Core API

#include <rapidcheck.h>

Common Generators

*rc::gen::arbitrary<int>()           // Any int
rc::gen::inRange(0, 100)            // Int in range [0, 100)
rc::gen::arbitrary<float>()          // Any float
rc::gen::arbitrary<std::string>()    // Any string (including empty)
rc::gen::string()                    // Any string
rc::gen::string(std::regex("[a-z]+")) // Regex-constrained string
rc::gen::arbitrary<bool>()           // true or false
rc::gen::arbitrary<char>()           // Any char
rc::gen::container<std::vector<int>>(rc::gen::arbitrary<int>())  // Vector of ints
rc::gen::container<std::map<std::string, int>>(...)  // Map
rc::gen::pair(rc::gen::arbitrary<int>(), rc::gen::arbitrary<std::string>())  // Pairs
rc::gen::oneOf(rc::gen::value(0), rc::gen::value(1))  // One of several values

Writing Property Tests (rc::prop / RC_ASSERT)

#include <rapidcheck.h>
#include <rapidcheck/gtest.h>
#include <algorithm>
#include <vector>

RC_GTEST_PROP(sort, length_invariant, (std::vector<int> v)) {
    auto original_len = v.size();
    std::sort(v.begin(), v.end());
    RC_ASSERT(v.size() == original_len);
}

RC_GTEST_PROP(sort, is_ordered, (std::vector<int> v)) {
    std::sort(v.begin(), v.end());
    for (size_t i = 0; i + 1 < v.size(); i++) {
        RC_ASSERT(v[i] <= v[i + 1]);
    }
}

Assertion Macros

RC_ASSERT(expr);              // Like assert, but with proper failure reporting
RC_ASSERT(expr == expected);  // Equality check with nice output
RC_ASSERT_THROWS(expr);       // Assert that expression throws

Preconditions

RC_GTEST_PROP(divide, properties, (int a, int b)) {
    RC_PRE(b != 0);  // Skip when b is zero
    int result = a / b;
    // At minimum, this shouldn't crash
    (void)result;
}

Custom Generators

struct User {
    std::string name;
    int age;
};

// Custom generator using rc::gen::apply
auto genUser = rc::gen::apply([](const std::string& name, int age) {
    return User{name, age};
}, rc::gen::string<std::string>(),
   rc::gen::inRange(0, 150));

RC_GTEST_PROP(user, has_name, ()) {
    auto user = *genUser;
    RC_ASSERT(!user.name.empty());
}

// Or using rc::gen::exec
auto genUser2 = rc::gen::exec([](User& u) {
    u.name = *rc::gen::string<std::string>();
    u.age = *rc::gen::inRange(0, 150);
});

// Or simpler: define a generator function
User genUser3() {
    return User{
        *rc::gen::string<std::string>(),
        *rc::gen::inRange(0, 150)
    };
}

Using * to Unpack Generators

Inside property tests, * unpacks/draws from a generator:

RC_GTEST_PROP(my_test, property, ()) {
    // Inside a property body, * unpacks a generator
    auto x = *rc::gen::arbitrary<int>();

    // For parameterized tests, parameters are automatically drawn
}

Tagged Tests (alternative to RC_GTEST_PROP)

TEST(MySuite, SortProperties) {
    rc::check("length is invariant",
              [](const std::vector<int>& v) {
                  auto original_len = v.size();
                  auto sorted = v;
                  std::sort(sorted.begin(), sorted.end());
                  RC_ASSERT(sorted.size() == original_len);
              });

    rc::check("result is ordered",
              [](const std::vector<int>& v) {
                  auto sorted = v;
                  std::sort(sorted.begin(), sorted.end());
                  for (size_t i = 0; i + 1 < sorted.size(); i++) {
                      RC_ASSERT(sorted[i] <= sorted[i + 1]);
                  }
              });
}

Configuration

// Per-test configuration
RC_GTEST_PROP(sort, heavy_test, (std::vector<int> v)) {
    RC_DISCARD(v.size() > 1000);  // Skip large inputs for performance
    // ...
}

// Set number of test cases globally:
// Pass --rc-params "{\"numTests\": 1000}" on command line
// Or set via RC_PARAMS environment variable

Running

# With GTest
cmake --build . && ./tests --gtest_filter="*sort*"
# Pass RapidCheck parameters via environment
RC_PARAMS='{"numTests":500}' ./tests

Key Differences from Other Frameworks

  • Uses * operator to "draw" values from generators (like Rust's ? but for generators)
  • RC_GTEST_PROP integrates directly with Google Test
  • RC_ASSERT is critical — regular assert won't give you proper shrinking reports
  • Parameters in RC_GTEST_PROP(name, desc, (params)) are automatically drawn from default generators — you don't need to call anything

SKILL.md

tessl.json

tile.json