Skip to content

Repository files navigation

Hypothesis for Delphi

A minimal property-based testing library for Delphi, inspired by Python's Hypothesis library.

Overview

Hypothesis for Delphi enables property-based testing using custom attributes and automatic value generation with shrinking support. Instead of writing individual test cases with specific values, you define properties that should hold true for all valid inputs, and Hypothesis generates test data automatically.

Property-Based Testing vs Traditional Testing

Traditional unit tests verify behavior with specific, hand-picked examples: "when I reverse 'hello', I get 'olleh'". Property-based testing takes a different approach by defining general rules: "reversing any string twice returns the original". Hypothesis then automatically generates hundreds of test cases to verify this property holds for empty strings, single characters, long strings, special characters, and edge cases you might not have thought of. When a property fails, Hypothesis automatically "shrinks" the failing input to find the simplest example that breaks your code, making debugging much easier. This approach catches corner cases that manual testing often misses, while requiring less test code to write and maintain. Property-based testing complements traditional example-based tests perfectly: use examples for specific known scenarios and edge cases, and use properties to verify general behavior across a wide range of inputs.

Features

  • Automatic Value Generation: Generate random test data based on declarative strategies
  • Smart Shrinking: When a test fails, automatically finds the minimal failing example
  • DUnitX Integration: Seamless integration with the DUnitX test framework
  • Type-Safe: Leverages Delphi's RTTI and generics for type safety
  • Configurable: Control iteration counts and random seeds for reproducibility

Requirements

  • Delphi 11 (or newer)
  • DUnitX test framework
  • Spring4D collections library

Installation

  1. Add the Delphi.Source\src folder to your project's search path
  2. Ensure DUnitX and Spring4D are available in your project
  3. Add the required units to your test project:
    • Hypothesis.Attributes - Strategy attribute declarations
    • Hypothesis.Runner - Test execution helper
    • Hypothesis.Core - Core test runner (automatically included)
    • Hypothesis.Generators.* - Value generators (automatically included)

Quick Start

unit MyTests;
interfaceuses
DUnitX.TestFramework,
Hypothesis.Attributes,
Hypothesis.Runner;
type
[TestFixture]
TMyPropertyTests = classpublic
[Test]
procedureRunTestStringReverse;
[ForAll(100)]
procedureTestStringReverse([StringAlpha(0, 50)] const Text: string);
end;
implementationuses
System.SysUtils;
functionReverseString(const S: string): string;
begin
Result := '';
forvar I := S.Length downto1do
Result := Result + S[I];
end;
procedureTMyPropertyTests.RunTestStringReverse;
begin
THypothesis.Run(Self, 'TestStringReverse');
end;
procedureTMyPropertyTests.TestStringReverse(const Text: string);
beginvar Reversed := ReverseString(Text);
var DoubleReversed := ReverseString(Reversed);
Assert.AreEqual(Text, DoubleReversed,
'Reversing a string twice should give the original string');
end;
end.

Strategy Attributes

Integer Strategies

IntRange(Min, Max)

Generates integers within the specified range (inclusive).

[Test]
procedureRunTestIntRange;
[ForAll(100)]
procedureTestIntRange([IntRange(-100, 100)] constValue: Integer);
// ImplementationprocedureTMyTests.RunTestIntRange;
begin
THypothesis.Run(Self, 'TestIntRange');
end;

IntPositive(Max)

Generates positive integers from 1 to Max (inclusive).

[ForAll(100)]
procedureTestPositive([IntPositive(1000)] const Count: Integer);

IntNegative(Min)

Generates negative integers from Min to -1 (inclusive).

[ForAll(100)]
procedureTestNegative([IntNegative(-1000)] const Debt: Integer);

IntNonZero(Min, Max)

Generates integers in the range Min to Max, excluding zero.

[ForAll(100)]
procedureTestNonZero([IntNonZero(-100, 100)] const Divisor: Integer);

String Strategies

StringGen(MinLen, MaxLen)

Generates strings with arbitrary printable characters.

[ForAll(100)]
procedureTestAnyString([StringGen(0, 100)] const Text: string);

StringAlpha(MinLen, MaxLen)

Generates strings containing only alphabetic characters (A-Z, a-z).

[ForAll(100)]
procedureTestAlphaString([StringAlpha(1, 50)] constName: string);

StringNumeric(MinLen, MaxLen)

Generates strings containing only numeric digits (0-9).

[ForAll(100)]
procedureTestNumericString([StringNumeric(5, 10)] const Code: string);

Boolean Strategies

Boolean

Generates boolean values (True or False).

[ForAll(100)]
procedureTestBoolean([Boolean] const Flag: Boolean);

Float/Double Strategies

FloatRange(Min, Max, AllowNaN, AllowInfinity)

Generates floating-point values within the specified range.

[ForAll(100)]
procedureTestFloatRange([FloatRange(-100.0, 100.0)] constValue: Double);
// With special values
[ForAll(100)]
procedureTestFloatSpecial([FloatRange(-10.0, 10.0, True, True)] constValue: Double);

FloatPositive(Max)

Generates positive floating-point values greater than zero.

[ForAll(100)]
procedureTestPositiveFloat([FloatPositive(1000.0)] const Amount: Double);

FloatNegative(Min)

Generates negative floating-point values less than zero.

[ForAll(100)]
procedureTestNegativeFloat([FloatNegative(-1000.0)] const Debt: Double);

FloatUnit

Generates floating-point values in the unit interval [0.0, 1.0].

[ForAll(100)]
procedureTestProbability([FloatUnit] const Probability: Double);

Date/DateTime Strategies

DateRange(MinYear, MaxYear)

Generates date values (TDate) within the specified year range.

[ForAll(100)]
procedureTestDate([DateRange(1900, 2100)] const Date: TDate);

DateTimeRange(MinYear, MaxYear)

Generates datetime values (TDateTime) with both date and time components.

[ForAll(100)]
procedureTestDateTime([DateTimeRange(1900, 2100)] const DT: TDateTime);

DateRecent(Days)

Generates recent dates within the specified number of days from today.

[ForAll(100)]
procedureTestRecentDate([DateRecent(30)] const Date: TDate);

TimeRange

Generates time values (TTime) representing time of day (00:00:00 to 23:59:59).

[ForAll(100)]
procedureTestTime([TimeRange] const Time: TTime);

Collection Strategies

Collection generators (arrays, lists, dictionaries) require manual instantiation due to Delphi's compile-time constant requirements in attributes. Hypothesis provides convenient helper methods to simplify collection generation.

Using Collection Helpers

Instead of using attributes, use the helper methods with the generator-based Run overload:

type
[TestFixture]
TCollectionTests = classpublic
[Test]
procedureRunTestArraySum;
procedureTestArraySum(const Values: TArray<Int64>);
end;
implementationprocedureTCollectionTests.RunTestArraySum;
begin
THypothesis.Run(Self, 'TestArraySum', [
THypothesis.ArrayOfIntegers(5, 10, 1, 100)
], 50);
end;
procedureTCollectionTests.TestArraySum(const Values: TArray<Int64>);
var
Sum: Int64;
I: Integer;
begin
Sum := 0;
for I := 0to High(Values) do
Sum := Sum + Values[I];
Assert.IsTrue(Sum > 0, 'Sum of positive integers should be positive');
Assert.IsTrue(Length(Values) >= 5);
Assert.IsTrue(Length(Values) <= 10);
end;

Array Helper Methods

  • ArrayOfIntegers(MinCount, MaxCount, MinValue, MaxValue, ExcludeZero) - Arrays of Int64
  • ArrayOfStrings(MinCount, MaxCount, MinLen, MaxLen, CharSet) - Arrays of strings
  • ArrayOfFloats(MinCount, MaxCount, MinValue, MaxValue, AllowNaN, AllowInfinity) - Arrays of Double
  • ArrayOfBooleans(MinCount, MaxCount) - Arrays of Boolean
  • ArrayOf(MinCount, MaxCount, ElementGenerator, ElementTypeInfo) - Generic array generator
// Generate array of 3-7 strings, each 5-15 characters long
THypothesis.ArrayOfStrings(3, 7, 5, 15, TStringCharSet.Alpha)
// Generate array of 10-20 floats between -1.0 and 1.0
THypothesis.ArrayOfFloats(10, 20, -1.0, 1.0)

List Helper Methods

Lists use Spring4D's IList<T> interface:

  • ListOfIntegers(MinCount, MaxCount, MinValue, MaxValue, ExcludeZero) - Lists of Int64
  • ListOfStrings(MinCount, MaxCount, MinLen, MaxLen, CharSet) - Lists of strings
  • ListOfFloats(MinCount, MaxCount, MinValue, MaxValue, AllowNaN, AllowInfinity) - Lists of Double
  • ListOfBooleans(MinCount, MaxCount) - Lists of Boolean
  • ListOf(MinCount, MaxCount, ElementGenerator, ElementTypeInfo) - Generic list generator
procedureTTests.RunTestListContains;
begin
THypothesis.Run(Self, 'TestListContains', [
THypothesis.ListOfIntegers(5, 10, 1, 100)
], 50);
end;
procedureTTests.TestListContains(const Values: IList<Int64>);
beginif Values.Count > 0then
Assert.IsTrue(Values.Contains(Values[0]));
end;

Dictionary Helper Methods

Dictionaries use Spring4D's IDictionary<K,V> interface:

  • DictIntegerToString(MinCount, MaxCount, KeyMin, KeyMax, ValueMinLen, ValueMaxLen, ValueCharSet) - Int64 → string
  • DictStringToInteger(MinCount, MaxCount, KeyMinLen, KeyMaxLen, KeyCharSet, ValueMin, ValueMax) - string → Int64
  • DictStringToString(MinCount, MaxCount, KeyMinLen, KeyMaxLen, KeyCharSet, ValueMinLen, ValueMaxLen, ValueCharSet) - string → string
  • DictOf(MinCount, MaxCount, KeyGenerator, ValueGenerator, KeyTypeInfo, ValueTypeInfo) - Generic dictionary generator
// Generate dictionary with 3-8 entries, Int64 keys (1-100), string values (5-15 chars)
THypothesis.DictIntegerToString(3, 8, 1, 100, 5, 15, TStringCharSet.Alpha)

Multiple Collection Parameters

You can pass multiple generators to test interactions between collections:

procedureTTests.RunTestArrayConcatenation;
begin
THypothesis.Run(Self, 'TestArrayConcatenation', [
THypothesis.ArrayOfIntegers(3, 5, 1, 100),
THypothesis.ArrayOfIntegers(3, 5, 1, 100)
], 50);
end;
procedureTTests.TestArrayConcatenation(const A1, A2: TArray<Int64>);
var
Combined: TArray<Int64>;
begin
SetLength(Combined, Length(A1) + Length(A2));
// ... concatenation logic ...
Assert.AreEqual(Length(A1) + Length(A2), Length(Combined));
end;

Configuring Iterations

Use the ForAll attribute to specify the number of test iterations (default: 10).

[ForAll(1000)]
procedureTestWithManyIterations([IntRange(1, 100)] constValue: Integer);

Multiple Parameters

Property tests can accept multiple parameters with different strategies.

Using Attribute-Based Generation

[ForAll(100)]
procedureTestAddition([IntRange(-1000, 1000)] const A: Integer;
[IntRange(-1000, 1000)] const B: Integer);
beginconst Sum = Int64(A) + Int64(B);
Assert.AreEqual(Sum, Int64(B) + Int64(A), 'Addition should be commutative');
end;

Using Generator-Based Execution

For more complex scenarios or when using collections, you can pass generators directly:

[Test]
procedureRunTestComplexScenario;
procedureTestComplexScenario(const Values: TArray<Int64>; constName: string);
// ImplementationprocedureTTests.RunTestComplexScenario;
begin
THypothesis.Run(Self, 'TestComplexScenario', [
THypothesis.ArrayOfIntegers(5, 10, 1, 100),
THypothesis.StringAlpha(3, 20) // Note: StringAlpha not yet implemented as helper
], 50);
end;

Note: Currently, only collection helpers are available. String and other type helpers are planned for future versions.

How Shrinking Works

When a test fails, Hypothesis automatically searches for a simpler failing example:

  • Integers: Binary search towards zero within valid range
  • Strings: Reduce length and simplify characters
  • Booleans: True shrinks to False
  • Floats: Special values → zero → nearest integer → halfway towards zero
  • Dates: Year shrinks towards 2000, month towards January, day towards 1st
  • DateTimes: Date components shrink as above, time shrinks towards midnight
  • Times: Hour/minute/second/millisecond each shrink towards zero

Example failure output:

Property test failed on iteration 47/100
Original values: 'AbCdEfGhIjKlMnOp', 42
Minimal failing example: 'A', 1
Seed: 12345678

Best Practices

  1. Keep properties simple: Each test should verify one property
  2. Use appropriate ranges: Don't generate more values than needed
  3. Handle edge cases: Consider empty strings, zero, negative numbers
  4. Use meaningful parameter names: The ParamName helps in error messages
  5. Start with fewer iterations: Use 10-100 iterations during development, increase for CI

Examples

The project includes two types of examples:

Example Code

Delphi.Source/examples/Hypothesis.Examples.pas - Simple utility classes demonstrating testable code:

  • TStringUtils: String manipulation functions
  • TMathUtils: Mathematical operations

Example Tests

Delphi.Source/examples/Hypothesis.Examples.Tests.pas - Property-based tests for the example code:

  • String reversal properties (involutive, length preservation)
  • Arithmetic properties (commutativity, associativity)
  • Consistency checks

Framework Tests

Delphi.Source/tests/Hypothesis.Core.Tests.pas - Comprehensive test suite including:

  • Integer properties (reverse, addition, absolute value)
  • String properties (reverse, concatenation, uppercase)
  • Strategy validation (positive, negative, non-zero, alpha, numeric)
  • Combined integer and string properties

Architecture

The library consists of the following main components:

  1. Hypothesis.Attributes: Custom attributes for declaring strategies (ForAll, IntRange, StringAlpha, etc.)
  2. Hypothesis.Generators.*: Value generators with shrinking logic
    • Hypothesis.Generators.Interfaces: Base interface for all generators
    • Hypothesis.Generators.Integers: Integer value generation and shrinking
    • Hypothesis.Generators.Strings: String value generation and shrinking
    • Hypothesis.Generators.Factory: Creates appropriate generators from attributes
  3. Hypothesis.Core: Test runner with RTTI-based parameter inspection and shrinking orchestration
  4. Hypothesis.Runner: DUnitX framework integration helper (THypothesis.Run)
  5. Hypothesis.Exceptions: Custom exception types for property test failures

Limitations

Current implementation includes:

✅ Supported Types:

  • Integers (Int64)
  • Strings (with multiple character sets)
  • Booleans
  • Floats/Doubles (with special values)
  • Dates, DateTimes, and Times
  • Collections (TArray, IList, IDictionary via helper methods)

⚠️ Collection Limitations:

  • Collections require manual instantiation using helper methods
  • Cannot use attribute-based generation for collections (Delphi language limitation)
  • Collection types are limited to: Int64, string, Double, Boolean

📋 Not Yet Implemented:

  • Records and custom object types
  • Advanced shrinking strategies
  • Database/persistence for test cases
  • Stateful testing
  • Custom strategy composition
  • Full generic collection support

Future versions may add support for records, advanced shrinking, and more flexible collection generators.

Troubleshooting

"Parameter has no strategy attribute"

Ensure all test method parameters have a strategy attribute (e.g., IntRange, StringAlpha).

"Unsupported attribute type"

Check that you're using one of the supported strategy attributes.

Tests are too slow

Reduce the number of iterations in the ForAll attribute or narrow the value ranges.

Contributing

This is an experimental library. Feedback and contributions are welcome.

License

[Specify your license here]

Acknowledgments

Inspired by the excellent Hypothesis library for Python.

About

Delphi implementation for the Python Hyopthesis library. Meant for unit testing strategies.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - GDKsoftware/Hypothesis4D: Delphi implementation for the Python Hyopthesis library. Meant for unit testing strategies. · GitHub
Skip to content

Repository files navigation

Hypothesis for Delphi

A minimal property-based testing library for Delphi, inspired by Python's Hypothesis library.

Overview

Hypothesis for Delphi enables property-based testing using custom attributes and automatic value generation with shrinking support. Instead of writing individual test cases with specific values, you define properties that should hold true for all valid inputs, and Hypothesis generates test data automatically.

Property-Based Testing vs Traditional Testing

Traditional unit tests verify behavior with specific, hand-picked examples: "when I reverse 'hello', I get 'olleh'". Property-based testing takes a different approach by defining general rules: "reversing any string twice returns the original". Hypothesis then automatically generates hundreds of test cases to verify this property holds for empty strings, single characters, long strings, special characters, and edge cases you might not have thought of. When a property fails, Hypothesis automatically "shrinks" the failing input to find the simplest example that breaks your code, making debugging much easier. This approach catches corner cases that manual testing often misses, while requiring less test code to write and maintain. Property-based testing complements traditional example-based tests perfectly: use examples for specific known scenarios and edge cases, and use properties to verify general behavior across a wide range of inputs.

Features

  • Automatic Value Generation: Generate random test data based on declarative strategies
  • Smart Shrinking: When a test fails, automatically finds the minimal failing example
  • DUnitX Integration: Seamless integration with the DUnitX test framework
  • Type-Safe: Leverages Delphi's RTTI and generics for type safety
  • Configurable: Control iteration counts and random seeds for reproducibility

Requirements

  • Delphi 11 (or newer)
  • DUnitX test framework
  • Spring4D collections library

Installation

  1. Add the Delphi.Source\src folder to your project's search path
  2. Ensure DUnitX and Spring4D are available in your project
  3. Add the required units to your test project:
    • Hypothesis.Attributes - Strategy attribute declarations
    • Hypothesis.Runner - Test execution helper
    • Hypothesis.Core - Core test runner (automatically included)
    • Hypothesis.Generators.* - Value generators (automatically included)

Quick Start

unit MyTests;
interfaceuses
DUnitX.TestFramework,
Hypothesis.Attributes,
Hypothesis.Runner;
type
[TestFixture]
TMyPropertyTests = classpublic
[Test]
procedureRunTestStringReverse;
[ForAll(100)]
procedureTestStringReverse([StringAlpha(0, 50)] const Text: string);
end;
implementationuses
System.SysUtils;
functionReverseString(const S: string): string;
begin
Result := '';
forvar I := S.Length downto1do
Result := Result + S[I];
end;
procedureTMyPropertyTests.RunTestStringReverse;
begin
THypothesis.Run(Self, 'TestStringReverse');
end;
procedureTMyPropertyTests.TestStringReverse(const Text: string);
beginvar Reversed := ReverseString(Text);
var DoubleReversed := ReverseString(Reversed);
Assert.AreEqual(Text, DoubleReversed,
'Reversing a string twice should give the original string');
end;
end.

Strategy Attributes

Integer Strategies

IntRange(Min, Max)

Generates integers within the specified range (inclusive).

[Test]
procedureRunTestIntRange;
[ForAll(100)]
procedureTestIntRange([IntRange(-100, 100)] constValue: Integer);
// ImplementationprocedureTMyTests.RunTestIntRange;
begin
THypothesis.Run(Self, 'TestIntRange');
end;

IntPositive(Max)

Generates positive integers from 1 to Max (inclusive).

[ForAll(100)]
procedureTestPositive([IntPositive(1000)] const Count: Integer);

IntNegative(Min)

Generates negative integers from Min to -1 (inclusive).

[ForAll(100)]
procedureTestNegative([IntNegative(-1000)] const Debt: Integer);

IntNonZero(Min, Max)

Generates integers in the range Min to Max, excluding zero.

[ForAll(100)]
procedureTestNonZero([IntNonZero(-100, 100)] const Divisor: Integer);

String Strategies

StringGen(MinLen, MaxLen)

Generates strings with arbitrary printable characters.

[ForAll(100)]
procedureTestAnyString([StringGen(0, 100)] const Text: string);

StringAlpha(MinLen, MaxLen)

Generates strings containing only alphabetic characters (A-Z, a-z).

[ForAll(100)]
procedureTestAlphaString([StringAlpha(1, 50)] constName: string);

StringNumeric(MinLen, MaxLen)

Generates strings containing only numeric digits (0-9).

[ForAll(100)]
procedureTestNumericString([StringNumeric(5, 10)] const Code: string);

Boolean Strategies

Boolean

Generates boolean values (True or False).

[ForAll(100)]
procedureTestBoolean([Boolean] const Flag: Boolean);

Float/Double Strategies

FloatRange(Min, Max, AllowNaN, AllowInfinity)

Generates floating-point values within the specified range.

[ForAll(100)]
procedureTestFloatRange([FloatRange(-100.0, 100.0)] constValue: Double);
// With special values
[ForAll(100)]
procedureTestFloatSpecial([FloatRange(-10.0, 10.0, True, True)] constValue: Double);

FloatPositive(Max)

Generates positive floating-point values greater than zero.

[ForAll(100)]
procedureTestPositiveFloat([FloatPositive(1000.0)] const Amount: Double);

FloatNegative(Min)

Generates negative floating-point values less than zero.

[ForAll(100)]
procedureTestNegativeFloat([FloatNegative(-1000.0)] const Debt: Double);

FloatUnit

Generates floating-point values in the unit interval [0.0, 1.0].

[ForAll(100)]
procedureTestProbability([FloatUnit] const Probability: Double);

Date/DateTime Strategies

DateRange(MinYear, MaxYear)

Generates date values (TDate) within the specified year range.

[ForAll(100)]
procedureTestDate([DateRange(1900, 2100)] const Date: TDate);

DateTimeRange(MinYear, MaxYear)

Generates datetime values (TDateTime) with both date and time components.

[ForAll(100)]
procedureTestDateTime([DateTimeRange(1900, 2100)] const DT: TDateTime);

DateRecent(Days)

Generates recent dates within the specified number of days from today.

[ForAll(100)]
procedureTestRecentDate([DateRecent(30)] const Date: TDate);

TimeRange

Generates time values (TTime) representing time of day (00:00:00 to 23:59:59).

[ForAll(100)]
procedureTestTime([TimeRange] const Time: TTime);

Collection Strategies

Collection generators (arrays, lists, dictionaries) require manual instantiation due to Delphi's compile-time constant requirements in attributes. Hypothesis provides convenient helper methods to simplify collection generation.

Using Collection Helpers

Instead of using attributes, use the helper methods with the generator-based Run overload:

type
[TestFixture]
TCollectionTests = classpublic
[Test]
procedureRunTestArraySum;
procedureTestArraySum(const Values: TArray<Int64>);
end;
implementationprocedureTCollectionTests.RunTestArraySum;
begin
THypothesis.Run(Self, 'TestArraySum', [
THypothesis.ArrayOfIntegers(5, 10, 1, 100)
], 50);
end;
procedureTCollectionTests.TestArraySum(const Values: TArray<Int64>);
var
Sum: Int64;
I: Integer;
begin
Sum := 0;
for I := 0to High(Values) do
Sum := Sum + Values[I];
Assert.IsTrue(Sum > 0, 'Sum of positive integers should be positive');
Assert.IsTrue(Length(Values) >= 5);
Assert.IsTrue(Length(Values) <= 10);
end;

Array Helper Methods

  • ArrayOfIntegers(MinCount, MaxCount, MinValue, MaxValue, ExcludeZero) - Arrays of Int64
  • ArrayOfStrings(MinCount, MaxCount, MinLen, MaxLen, CharSet) - Arrays of strings
  • ArrayOfFloats(MinCount, MaxCount, MinValue, MaxValue, AllowNaN, AllowInfinity) - Arrays of Double
  • ArrayOfBooleans(MinCount, MaxCount) - Arrays of Boolean
  • ArrayOf(MinCount, MaxCount, ElementGenerator, ElementTypeInfo) - Generic array generator
// Generate array of 3-7 strings, each 5-15 characters long
THypothesis.ArrayOfStrings(3, 7, 5, 15, TStringCharSet.Alpha)
// Generate array of 10-20 floats between -1.0 and 1.0
THypothesis.ArrayOfFloats(10, 20, -1.0, 1.0)

List Helper Methods

Lists use Spring4D's IList<T> interface:

  • ListOfIntegers(MinCount, MaxCount, MinValue, MaxValue, ExcludeZero) - Lists of Int64
  • ListOfStrings(MinCount, MaxCount, MinLen, MaxLen, CharSet) - Lists of strings
  • ListOfFloats(MinCount, MaxCount, MinValue, MaxValue, AllowNaN, AllowInfinity) - Lists of Double
  • ListOfBooleans(MinCount, MaxCount) - Lists of Boolean
  • ListOf(MinCount, MaxCount, ElementGenerator, ElementTypeInfo) - Generic list generator
procedureTTests.RunTestListContains;
begin
THypothesis.Run(Self, 'TestListContains', [
THypothesis.ListOfIntegers(5, 10, 1, 100)
], 50);
end;
procedureTTests.TestListContains(const Values: IList<Int64>);
beginif Values.Count > 0then
Assert.IsTrue(Values.Contains(Values[0]));
end;

Dictionary Helper Methods

Dictionaries use Spring4D's IDictionary<K,V> interface:

  • DictIntegerToString(MinCount, MaxCount, KeyMin, KeyMax, ValueMinLen, ValueMaxLen, ValueCharSet) - Int64 → string
  • DictStringToInteger(MinCount, MaxCount, KeyMinLen, KeyMaxLen, KeyCharSet, ValueMin, ValueMax) - string → Int64
  • DictStringToString(MinCount, MaxCount, KeyMinLen, KeyMaxLen, KeyCharSet, ValueMinLen, ValueMaxLen, ValueCharSet) - string → string
  • DictOf(MinCount, MaxCount, KeyGenerator, ValueGenerator, KeyTypeInfo, ValueTypeInfo) - Generic dictionary generator
// Generate dictionary with 3-8 entries, Int64 keys (1-100), string values (5-15 chars)
THypothesis.DictIntegerToString(3, 8, 1, 100, 5, 15, TStringCharSet.Alpha)

Multiple Collection Parameters

You can pass multiple generators to test interactions between collections:

procedureTTests.RunTestArrayConcatenation;
begin
THypothesis.Run(Self, 'TestArrayConcatenation', [
THypothesis.ArrayOfIntegers(3, 5, 1, 100),
THypothesis.ArrayOfIntegers(3, 5, 1, 100)
], 50);
end;
procedureTTests.TestArrayConcatenation(const A1, A2: TArray<Int64>);
var
Combined: TArray<Int64>;
begin
SetLength(Combined, Length(A1) + Length(A2));
// ... concatenation logic ...
Assert.AreEqual(Length(A1) + Length(A2), Length(Combined));
end;

Configuring Iterations

Use the ForAll attribute to specify the number of test iterations (default: 10).

[ForAll(1000)]
procedureTestWithManyIterations([IntRange(1, 100)] constValue: Integer);

Multiple Parameters

Property tests can accept multiple parameters with different strategies.

Using Attribute-Based Generation

[ForAll(100)]
procedureTestAddition([IntRange(-1000, 1000)] const A: Integer;
[IntRange(-1000, 1000)] const B: Integer);
beginconst Sum = Int64(A) + Int64(B);
Assert.AreEqual(Sum, Int64(B) + Int64(A), 'Addition should be commutative');
end;

Using Generator-Based Execution

For more complex scenarios or when using collections, you can pass generators directly:

[Test]
procedureRunTestComplexScenario;
procedureTestComplexScenario(const Values: TArray<Int64>; constName: string);
// ImplementationprocedureTTests.RunTestComplexScenario;
begin
THypothesis.Run(Self, 'TestComplexScenario', [
THypothesis.ArrayOfIntegers(5, 10, 1, 100),
THypothesis.StringAlpha(3, 20) // Note: StringAlpha not yet implemented as helper
], 50);
end;

Note: Currently, only collection helpers are available. String and other type helpers are planned for future versions.

How Shrinking Works

When a test fails, Hypothesis automatically searches for a simpler failing example:

  • Integers: Binary search towards zero within valid range
  • Strings: Reduce length and simplify characters
  • Booleans: True shrinks to False
  • Floats: Special values → zero → nearest integer → halfway towards zero
  • Dates: Year shrinks towards 2000, month towards January, day towards 1st
  • DateTimes: Date components shrink as above, time shrinks towards midnight
  • Times: Hour/minute/second/millisecond each shrink towards zero

Example failure output:

Property test failed on iteration 47/100
Original values: 'AbCdEfGhIjKlMnOp', 42
Minimal failing example: 'A', 1
Seed: 12345678

Best Practices

  1. Keep properties simple: Each test should verify one property
  2. Use appropriate ranges: Don't generate more values than needed
  3. Handle edge cases: Consider empty strings, zero, negative numbers
  4. Use meaningful parameter names: The ParamName helps in error messages
  5. Start with fewer iterations: Use 10-100 iterations during development, increase for CI

Examples

The project includes two types of examples:

Example Code

Delphi.Source/examples/Hypothesis.Examples.pas - Simple utility classes demonstrating testable code:

  • TStringUtils: String manipulation functions
  • TMathUtils: Mathematical operations

Example Tests

Delphi.Source/examples/Hypothesis.Examples.Tests.pas - Property-based tests for the example code:

  • String reversal properties (involutive, length preservation)
  • Arithmetic properties (commutativity, associativity)
  • Consistency checks

Framework Tests

Delphi.Source/tests/Hypothesis.Core.Tests.pas - Comprehensive test suite including:

  • Integer properties (reverse, addition, absolute value)
  • String properties (reverse, concatenation, uppercase)
  • Strategy validation (positive, negative, non-zero, alpha, numeric)
  • Combined integer and string properties

Architecture

The library consists of the following main components:

  1. Hypothesis.Attributes: Custom attributes for declaring strategies (ForAll, IntRange, StringAlpha, etc.)
  2. Hypothesis.Generators.*: Value generators with shrinking logic
    • Hypothesis.Generators.Interfaces: Base interface for all generators
    • Hypothesis.Generators.Integers: Integer value generation and shrinking
    • Hypothesis.Generators.Strings: String value generation and shrinking
    • Hypothesis.Generators.Factory: Creates appropriate generators from attributes
  3. Hypothesis.Core: Test runner with RTTI-based parameter inspection and shrinking orchestration
  4. Hypothesis.Runner: DUnitX framework integration helper (THypothesis.Run)
  5. Hypothesis.Exceptions: Custom exception types for property test failures

Limitations

Current implementation includes:

✅ Supported Types:

  • Integers (Int64)
  • Strings (with multiple character sets)
  • Booleans
  • Floats/Doubles (with special values)
  • Dates, DateTimes, and Times
  • Collections (TArray, IList, IDictionary via helper methods)

⚠️ Collection Limitations:

  • Collections require manual instantiation using helper methods
  • Cannot use attribute-based generation for collections (Delphi language limitation)
  • Collection types are limited to: Int64, string, Double, Boolean

📋 Not Yet Implemented:

  • Records and custom object types
  • Advanced shrinking strategies
  • Database/persistence for test cases
  • Stateful testing
  • Custom strategy composition
  • Full generic collection support

Future versions may add support for records, advanced shrinking, and more flexible collection generators.

Troubleshooting

"Parameter has no strategy attribute"

Ensure all test method parameters have a strategy attribute (e.g., IntRange, StringAlpha).

"Unsupported attribute type"

Check that you're using one of the supported strategy attributes.

Tests are too slow

Reduce the number of iterations in the ForAll attribute or narrow the value ranges.

Contributing

This is an experimental library. Feedback and contributions are welcome.

License

[Specify your license here]

Acknowledgments

Inspired by the excellent Hypothesis library for Python.

About

Delphi implementation for the Python Hyopthesis library. Meant for unit testing strategies.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - GDKsoftware/Hypothesis4D: Delphi implementation for the Python Hyopthesis library. Meant for unit testing strategies. · GitHub
Skip to content

Repository files navigation

Hypothesis for Delphi

A minimal property-based testing library for Delphi, inspired by Python's Hypothesis library.

Overview

Hypothesis for Delphi enables property-based testing using custom attributes and automatic value generation with shrinking support. Instead of writing individual test cases with specific values, you define properties that should hold true for all valid inputs, and Hypothesis generates test data automatically.

Property-Based Testing vs Traditional Testing

Traditional unit tests verify behavior with specific, hand-picked examples: "when I reverse 'hello', I get 'olleh'". Property-based testing takes a different approach by defining general rules: "reversing any string twice returns the original". Hypothesis then automatically generates hundreds of test cases to verify this property holds for empty strings, single characters, long strings, special characters, and edge cases you might not have thought of. When a property fails, Hypothesis automatically "shrinks" the failing input to find the simplest example that breaks your code, making debugging much easier. This approach catches corner cases that manual testing often misses, while requiring less test code to write and maintain. Property-based testing complements traditional example-based tests perfectly: use examples for specific known scenarios and edge cases, and use properties to verify general behavior across a wide range of inputs.

Features

  • Automatic Value Generation: Generate random test data based on declarative strategies
  • Smart Shrinking: When a test fails, automatically finds the minimal failing example
  • DUnitX Integration: Seamless integration with the DUnitX test framework
  • Type-Safe: Leverages Delphi's RTTI and generics for type safety
  • Configurable: Control iteration counts and random seeds for reproducibility

Requirements

  • Delphi 11 (or newer)
  • DUnitX test framework
  • Spring4D collections library

Installation

  1. Add the Delphi.Source\src folder to your project's search path
  2. Ensure DUnitX and Spring4D are available in your project
  3. Add the required units to your test project:
    • Hypothesis.Attributes - Strategy attribute declarations
    • Hypothesis.Runner - Test execution helper
    • Hypothesis.Core - Core test runner (automatically included)
    • Hypothesis.Generators.* - Value generators (automatically included)

Quick Start

unit MyTests;
interfaceuses
DUnitX.TestFramework,
Hypothesis.Attributes,
Hypothesis.Runner;
type
[TestFixture]
TMyPropertyTests = classpublic
[Test]
procedureRunTestStringReverse;
[ForAll(100)]
procedureTestStringReverse([StringAlpha(0, 50)] const Text: string);
end;
implementationuses
System.SysUtils;
functionReverseString(const S: string): string;
begin
Result := '';
forvar I := S.Length downto1do
Result := Result + S[I];
end;
procedureTMyPropertyTests.RunTestStringReverse;
begin
THypothesis.Run(Self, 'TestStringReverse');
end;
procedureTMyPropertyTests.TestStringReverse(const Text: string);
beginvar Reversed := ReverseString(Text);
var DoubleReversed := ReverseString(Reversed);
Assert.AreEqual(Text, DoubleReversed,
'Reversing a string twice should give the original string');
end;
end.

Strategy Attributes

Integer Strategies

IntRange(Min, Max)

Generates integers within the specified range (inclusive).

[Test]
procedureRunTestIntRange;
[ForAll(100)]
procedureTestIntRange([IntRange(-100, 100)] constValue: Integer);
// ImplementationprocedureTMyTests.RunTestIntRange;
begin
THypothesis.Run(Self, 'TestIntRange');
end;

IntPositive(Max)

Generates positive integers from 1 to Max (inclusive).

[ForAll(100)]
procedureTestPositive([IntPositive(1000)] const Count: Integer);

IntNegative(Min)

Generates negative integers from Min to -1 (inclusive).

[ForAll(100)]
procedureTestNegative([IntNegative(-1000)] const Debt: Integer);

IntNonZero(Min, Max)

Generates integers in the range Min to Max, excluding zero.

[ForAll(100)]
procedureTestNonZero([IntNonZero(-100, 100)] const Divisor: Integer);

String Strategies

StringGen(MinLen, MaxLen)

Generates strings with arbitrary printable characters.

[ForAll(100)]
procedureTestAnyString([StringGen(0, 100)] const Text: string);

StringAlpha(MinLen, MaxLen)

Generates strings containing only alphabetic characters (A-Z, a-z).

[ForAll(100)]
procedureTestAlphaString([StringAlpha(1, 50)] constName: string);

StringNumeric(MinLen, MaxLen)

Generates strings containing only numeric digits (0-9).

[ForAll(100)]
procedureTestNumericString([StringNumeric(5, 10)] const Code: string);

Boolean Strategies

Boolean

Generates boolean values (True or False).

[ForAll(100)]
procedureTestBoolean([Boolean] const Flag: Boolean);

Float/Double Strategies

FloatRange(Min, Max, AllowNaN, AllowInfinity)

Generates floating-point values within the specified range.

[ForAll(100)]
procedureTestFloatRange([FloatRange(-100.0, 100.0)] constValue: Double);
// With special values
[ForAll(100)]
procedureTestFloatSpecial([FloatRange(-10.0, 10.0, True, True)] constValue: Double);

FloatPositive(Max)

Generates positive floating-point values greater than zero.

[ForAll(100)]
procedureTestPositiveFloat([FloatPositive(1000.0)] const Amount: Double);

FloatNegative(Min)

Generates negative floating-point values less than zero.

[ForAll(100)]
procedureTestNegativeFloat([FloatNegative(-1000.0)] const Debt: Double);

FloatUnit

Generates floating-point values in the unit interval [0.0, 1.0].

[ForAll(100)]
procedureTestProbability([FloatUnit] const Probability: Double);

Date/DateTime Strategies

DateRange(MinYear, MaxYear)

Generates date values (TDate) within the specified year range.

[ForAll(100)]
procedureTestDate([DateRange(1900, 2100)] const Date: TDate);

DateTimeRange(MinYear, MaxYear)

Generates datetime values (TDateTime) with both date and time components.

[ForAll(100)]
procedureTestDateTime([DateTimeRange(1900, 2100)] const DT: TDateTime);

DateRecent(Days)

Generates recent dates within the specified number of days from today.

[ForAll(100)]
procedureTestRecentDate([DateRecent(30)] const Date: TDate);

TimeRange

Generates time values (TTime) representing time of day (00:00:00 to 23:59:59).

[ForAll(100)]
procedureTestTime([TimeRange] const Time: TTime);

Collection Strategies

Collection generators (arrays, lists, dictionaries) require manual instantiation due to Delphi's compile-time constant requirements in attributes. Hypothesis provides convenient helper methods to simplify collection generation.

Using Collection Helpers

Instead of using attributes, use the helper methods with the generator-based Run overload:

type
[TestFixture]
TCollectionTests = classpublic
[Test]
procedureRunTestArraySum;
procedureTestArraySum(const Values: TArray<Int64>);
end;
implementationprocedureTCollectionTests.RunTestArraySum;
begin
THypothesis.Run(Self, 'TestArraySum', [
THypothesis.ArrayOfIntegers(5, 10, 1, 100)
], 50);
end;
procedureTCollectionTests.TestArraySum(const Values: TArray<Int64>);
var
Sum: Int64;
I: Integer;
begin
Sum := 0;
for I := 0to High(Values) do
Sum := Sum + Values[I];
Assert.IsTrue(Sum > 0, 'Sum of positive integers should be positive');
Assert.IsTrue(Length(Values) >= 5);
Assert.IsTrue(Length(Values) <= 10);
end;

Array Helper Methods

  • ArrayOfIntegers(MinCount, MaxCount, MinValue, MaxValue, ExcludeZero) - Arrays of Int64
  • ArrayOfStrings(MinCount, MaxCount, MinLen, MaxLen, CharSet) - Arrays of strings
  • ArrayOfFloats(MinCount, MaxCount, MinValue, MaxValue, AllowNaN, AllowInfinity) - Arrays of Double
  • ArrayOfBooleans(MinCount, MaxCount) - Arrays of Boolean
  • ArrayOf(MinCount, MaxCount, ElementGenerator, ElementTypeInfo) - Generic array generator
// Generate array of 3-7 strings, each 5-15 characters long
THypothesis.ArrayOfStrings(3, 7, 5, 15, TStringCharSet.Alpha)
// Generate array of 10-20 floats between -1.0 and 1.0
THypothesis.ArrayOfFloats(10, 20, -1.0, 1.0)

List Helper Methods

Lists use Spring4D's IList<T> interface:

  • ListOfIntegers(MinCount, MaxCount, MinValue, MaxValue, ExcludeZero) - Lists of Int64
  • ListOfStrings(MinCount, MaxCount, MinLen, MaxLen, CharSet) - Lists of strings
  • ListOfFloats(MinCount, MaxCount, MinValue, MaxValue, AllowNaN, AllowInfinity) - Lists of Double
  • ListOfBooleans(MinCount, MaxCount) - Lists of Boolean
  • ListOf(MinCount, MaxCount, ElementGenerator, ElementTypeInfo) - Generic list generator
procedureTTests.RunTestListContains;
begin
THypothesis.Run(Self, 'TestListContains', [
THypothesis.ListOfIntegers(5, 10, 1, 100)
], 50);
end;
procedureTTests.TestListContains(const Values: IList<Int64>);
beginif Values.Count > 0then
Assert.IsTrue(Values.Contains(Values[0]));
end;

Dictionary Helper Methods

Dictionaries use Spring4D's IDictionary<K,V> interface:

  • DictIntegerToString(MinCount, MaxCount, KeyMin, KeyMax, ValueMinLen, ValueMaxLen, ValueCharSet) - Int64 → string
  • DictStringToInteger(MinCount, MaxCount, KeyMinLen, KeyMaxLen, KeyCharSet, ValueMin, ValueMax) - string → Int64
  • DictStringToString(MinCount, MaxCount, KeyMinLen, KeyMaxLen, KeyCharSet, ValueMinLen, ValueMaxLen, ValueCharSet) - string → string
  • DictOf(MinCount, MaxCount, KeyGenerator, ValueGenerator, KeyTypeInfo, ValueTypeInfo) - Generic dictionary generator
// Generate dictionary with 3-8 entries, Int64 keys (1-100), string values (5-15 chars)
THypothesis.DictIntegerToString(3, 8, 1, 100, 5, 15, TStringCharSet.Alpha)

Multiple Collection Parameters

You can pass multiple generators to test interactions between collections:

procedureTTests.RunTestArrayConcatenation;
begin
THypothesis.Run(Self, 'TestArrayConcatenation', [
THypothesis.ArrayOfIntegers(3, 5, 1, 100),
THypothesis.ArrayOfIntegers(3, 5, 1, 100)
], 50);
end;
procedureTTests.TestArrayConcatenation(const A1, A2: TArray<Int64>);
var
Combined: TArray<Int64>;
begin
SetLength(Combined, Length(A1) + Length(A2));
// ... concatenation logic ...
Assert.AreEqual(Length(A1) + Length(A2), Length(Combined));
end;

Configuring Iterations

Use the ForAll attribute to specify the number of test iterations (default: 10).

[ForAll(1000)]
procedureTestWithManyIterations([IntRange(1, 100)] constValue: Integer);

Multiple Parameters

Property tests can accept multiple parameters with different strategies.

Using Attribute-Based Generation

[ForAll(100)]
procedureTestAddition([IntRange(-1000, 1000)] const A: Integer;
[IntRange(-1000, 1000)] const B: Integer);
beginconst Sum = Int64(A) + Int64(B);
Assert.AreEqual(Sum, Int64(B) + Int64(A), 'Addition should be commutative');
end;

Using Generator-Based Execution

For more complex scenarios or when using collections, you can pass generators directly:

[Test]
procedureRunTestComplexScenario;
procedureTestComplexScenario(const Values: TArray<Int64>; constName: string);
// ImplementationprocedureTTests.RunTestComplexScenario;
begin
THypothesis.Run(Self, 'TestComplexScenario', [
THypothesis.ArrayOfIntegers(5, 10, 1, 100),
THypothesis.StringAlpha(3, 20) // Note: StringAlpha not yet implemented as helper
], 50);
end;

Note: Currently, only collection helpers are available. String and other type helpers are planned for future versions.

How Shrinking Works

When a test fails, Hypothesis automatically searches for a simpler failing example:

  • Integers: Binary search towards zero within valid range
  • Strings: Reduce length and simplify characters
  • Booleans: True shrinks to False
  • Floats: Special values → zero → nearest integer → halfway towards zero
  • Dates: Year shrinks towards 2000, month towards January, day towards 1st
  • DateTimes: Date components shrink as above, time shrinks towards midnight
  • Times: Hour/minute/second/millisecond each shrink towards zero

Example failure output:

Property test failed on iteration 47/100
Original values: 'AbCdEfGhIjKlMnOp', 42
Minimal failing example: 'A', 1
Seed: 12345678

Best Practices

  1. Keep properties simple: Each test should verify one property
  2. Use appropriate ranges: Don't generate more values than needed
  3. Handle edge cases: Consider empty strings, zero, negative numbers
  4. Use meaningful parameter names: The ParamName helps in error messages
  5. Start with fewer iterations: Use 10-100 iterations during development, increase for CI

Examples

The project includes two types of examples:

Example Code

Delphi.Source/examples/Hypothesis.Examples.pas - Simple utility classes demonstrating testable code:

  • TStringUtils: String manipulation functions
  • TMathUtils: Mathematical operations

Example Tests

Delphi.Source/examples/Hypothesis.Examples.Tests.pas - Property-based tests for the example code:

  • String reversal properties (involutive, length preservation)
  • Arithmetic properties (commutativity, associativity)
  • Consistency checks

Framework Tests

Delphi.Source/tests/Hypothesis.Core.Tests.pas - Comprehensive test suite including:

  • Integer properties (reverse, addition, absolute value)
  • String properties (reverse, concatenation, uppercase)
  • Strategy validation (positive, negative, non-zero, alpha, numeric)
  • Combined integer and string properties

Architecture

The library consists of the following main components:

  1. Hypothesis.Attributes: Custom attributes for declaring strategies (ForAll, IntRange, StringAlpha, etc.)
  2. Hypothesis.Generators.*: Value generators with shrinking logic
    • Hypothesis.Generators.Interfaces: Base interface for all generators
    • Hypothesis.Generators.Integers: Integer value generation and shrinking
    • Hypothesis.Generators.Strings: String value generation and shrinking
    • Hypothesis.Generators.Factory: Creates appropriate generators from attributes
  3. Hypothesis.Core: Test runner with RTTI-based parameter inspection and shrinking orchestration
  4. Hypothesis.Runner: DUnitX framework integration helper (THypothesis.Run)
  5. Hypothesis.Exceptions: Custom exception types for property test failures

Limitations

Current implementation includes:

✅ Supported Types:

  • Integers (Int64)
  • Strings (with multiple character sets)
  • Booleans
  • Floats/Doubles (with special values)
  • Dates, DateTimes, and Times
  • Collections (TArray, IList, IDictionary via helper methods)

⚠️ Collection Limitations:

  • Collections require manual instantiation using helper methods
  • Cannot use attribute-based generation for collections (Delphi language limitation)
  • Collection types are limited to: Int64, string, Double, Boolean

📋 Not Yet Implemented:

  • Records and custom object types
  • Advanced shrinking strategies
  • Database/persistence for test cases
  • Stateful testing
  • Custom strategy composition
  • Full generic collection support

Future versions may add support for records, advanced shrinking, and more flexible collection generators.

Troubleshooting

"Parameter has no strategy attribute"

Ensure all test method parameters have a strategy attribute (e.g., IntRange, StringAlpha).

"Unsupported attribute type"

Check that you're using one of the supported strategy attributes.

Tests are too slow

Reduce the number of iterations in the ForAll attribute or narrow the value ranges.

Contributing

This is an experimental library. Feedback and contributions are welcome.

License

[Specify your license here]

Acknowledgments

Inspired by the excellent Hypothesis library for Python.

About

Delphi implementation for the Python Hyopthesis library. Meant for unit testing strategies.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - GDKsoftware/Hypothesis4D: Delphi implementation for the Python Hyopthesis library. Meant for unit testing strategies. · GitHub
Skip to content

Repository files navigation

Hypothesis for Delphi

A minimal property-based testing library for Delphi, inspired by Python's Hypothesis library.

Overview

Hypothesis for Delphi enables property-based testing using custom attributes and automatic value generation with shrinking support. Instead of writing individual test cases with specific values, you define properties that should hold true for all valid inputs, and Hypothesis generates test data automatically.

Property-Based Testing vs Traditional Testing

Traditional unit tests verify behavior with specific, hand-picked examples: "when I reverse 'hello', I get 'olleh'". Property-based testing takes a different approach by defining general rules: "reversing any string twice returns the original". Hypothesis then automatically generates hundreds of test cases to verify this property holds for empty strings, single characters, long strings, special characters, and edge cases you might not have thought of. When a property fails, Hypothesis automatically "shrinks" the failing input to find the simplest example that breaks your code, making debugging much easier. This approach catches corner cases that manual testing often misses, while requiring less test code to write and maintain. Property-based testing complements traditional example-based tests perfectly: use examples for specific known scenarios and edge cases, and use properties to verify general behavior across a wide range of inputs.

Features

  • Automatic Value Generation: Generate random test data based on declarative strategies
  • Smart Shrinking: When a test fails, automatically finds the minimal failing example
  • DUnitX Integration: Seamless integration with the DUnitX test framework
  • Type-Safe: Leverages Delphi's RTTI and generics for type safety
  • Configurable: Control iteration counts and random seeds for reproducibility

Requirements

  • Delphi 11 (or newer)
  • DUnitX test framework
  • Spring4D collections library

Installation

  1. Add the Delphi.Source\src folder to your project's search path
  2. Ensure DUnitX and Spring4D are available in your project
  3. Add the required units to your test project:
    • Hypothesis.Attributes - Strategy attribute declarations
    • Hypothesis.Runner - Test execution helper
    • Hypothesis.Core - Core test runner (automatically included)
    • Hypothesis.Generators.* - Value generators (automatically included)

Quick Start

unit MyTests;
interfaceuses
DUnitX.TestFramework,
Hypothesis.Attributes,
Hypothesis.Runner;
type
[TestFixture]
TMyPropertyTests = classpublic
[Test]
procedureRunTestStringReverse;
[ForAll(100)]
procedureTestStringReverse([StringAlpha(0, 50)] const Text: string);
end;
implementationuses
System.SysUtils;
functionReverseString(const S: string): string;
begin
Result := '';
forvar I := S.Length downto1do
Result := Result + S[I];
end;
procedureTMyPropertyTests.RunTestStringReverse;
begin
THypothesis.Run(Self, 'TestStringReverse');
end;
procedureTMyPropertyTests.TestStringReverse(const Text: string);
beginvar Reversed := ReverseString(Text);
var DoubleReversed := ReverseString(Reversed);
Assert.AreEqual(Text, DoubleReversed,
'Reversing a string twice should give the original string');
end;
end.

Strategy Attributes

Integer Strategies

IntRange(Min, Max)

Generates integers within the specified range (inclusive).

[Test]
procedureRunTestIntRange;
[ForAll(100)]
procedureTestIntRange([IntRange(-100, 100)] constValue: Integer);
// ImplementationprocedureTMyTests.RunTestIntRange;
begin
THypothesis.Run(Self, 'TestIntRange');
end;

IntPositive(Max)

Generates positive integers from 1 to Max (inclusive).

[ForAll(100)]
procedureTestPositive([IntPositive(1000)] const Count: Integer);

IntNegative(Min)

Generates negative integers from Min to -1 (inclusive).

[ForAll(100)]
procedureTestNegative([IntNegative(-1000)] const Debt: Integer);

IntNonZero(Min, Max)

Generates integers in the range Min to Max, excluding zero.

[ForAll(100)]
procedureTestNonZero([IntNonZero(-100, 100)] const Divisor: Integer);

String Strategies

StringGen(MinLen, MaxLen)

Generates strings with arbitrary printable characters.

[ForAll(100)]
procedureTestAnyString([StringGen(0, 100)] const Text: string);

StringAlpha(MinLen, MaxLen)

Generates strings containing only alphabetic characters (A-Z, a-z).

[ForAll(100)]
procedureTestAlphaString([StringAlpha(1, 50)] constName: string);

StringNumeric(MinLen, MaxLen)

Generates strings containing only numeric digits (0-9).

[ForAll(100)]
procedureTestNumericString([StringNumeric(5, 10)] const Code: string);

Boolean Strategies

Boolean

Generates boolean values (True or False).

[ForAll(100)]
procedureTestBoolean([Boolean] const Flag: Boolean);

Float/Double Strategies

FloatRange(Min, Max, AllowNaN, AllowInfinity)

Generates floating-point values within the specified range.

[ForAll(100)]
procedureTestFloatRange([FloatRange(-100.0, 100.0)] constValue: Double);
// With special values
[ForAll(100)]
procedureTestFloatSpecial([FloatRange(-10.0, 10.0, True, True)] constValue: Double);

FloatPositive(Max)

Generates positive floating-point values greater than zero.

[ForAll(100)]
procedureTestPositiveFloat([FloatPositive(1000.0)] const Amount: Double);

FloatNegative(Min)

Generates negative floating-point values less than zero.

[ForAll(100)]
procedureTestNegativeFloat([FloatNegative(-1000.0)] const Debt: Double);

FloatUnit

Generates floating-point values in the unit interval [0.0, 1.0].

[ForAll(100)]
procedureTestProbability([FloatUnit] const Probability: Double);

Date/DateTime Strategies

DateRange(MinYear, MaxYear)

Generates date values (TDate) within the specified year range.

[ForAll(100)]
procedureTestDate([DateRange(1900, 2100)] const Date: TDate);

DateTimeRange(MinYear, MaxYear)

Generates datetime values (TDateTime) with both date and time components.

[ForAll(100)]
procedureTestDateTime([DateTimeRange(1900, 2100)] const DT: TDateTime);

DateRecent(Days)

Generates recent dates within the specified number of days from today.

[ForAll(100)]
procedureTestRecentDate([DateRecent(30)] const Date: TDate);

TimeRange

Generates time values (TTime) representing time of day (00:00:00 to 23:59:59).

[ForAll(100)]
procedureTestTime([TimeRange] const Time: TTime);

Collection Strategies

Collection generators (arrays, lists, dictionaries) require manual instantiation due to Delphi's compile-time constant requirements in attributes. Hypothesis provides convenient helper methods to simplify collection generation.

Using Collection Helpers

Instead of using attributes, use the helper methods with the generator-based Run overload:

type
[TestFixture]
TCollectionTests = classpublic
[Test]
procedureRunTestArraySum;
procedureTestArraySum(const Values: TArray<Int64>);
end;
implementationprocedureTCollectionTests.RunTestArraySum;
begin
THypothesis.Run(Self, 'TestArraySum', [
THypothesis.ArrayOfIntegers(5, 10, 1, 100)
], 50);
end;
procedureTCollectionTests.TestArraySum(const Values: TArray<Int64>);
var
Sum: Int64;
I: Integer;
begin
Sum := 0;
for I := 0to High(Values) do
Sum := Sum + Values[I];
Assert.IsTrue(Sum > 0, 'Sum of positive integers should be positive');
Assert.IsTrue(Length(Values) >= 5);
Assert.IsTrue(Length(Values) <= 10);
end;

Array Helper Methods

  • ArrayOfIntegers(MinCount, MaxCount, MinValue, MaxValue, ExcludeZero) - Arrays of Int64
  • ArrayOfStrings(MinCount, MaxCount, MinLen, MaxLen, CharSet) - Arrays of strings
  • ArrayOfFloats(MinCount, MaxCount, MinValue, MaxValue, AllowNaN, AllowInfinity) - Arrays of Double
  • ArrayOfBooleans(MinCount, MaxCount) - Arrays of Boolean
  • ArrayOf(MinCount, MaxCount, ElementGenerator, ElementTypeInfo) - Generic array generator
// Generate array of 3-7 strings, each 5-15 characters long
THypothesis.ArrayOfStrings(3, 7, 5, 15, TStringCharSet.Alpha)
// Generate array of 10-20 floats between -1.0 and 1.0
THypothesis.ArrayOfFloats(10, 20, -1.0, 1.0)

List Helper Methods

Lists use Spring4D's IList<T> interface:

  • ListOfIntegers(MinCount, MaxCount, MinValue, MaxValue, ExcludeZero) - Lists of Int64
  • ListOfStrings(MinCount, MaxCount, MinLen, MaxLen, CharSet) - Lists of strings
  • ListOfFloats(MinCount, MaxCount, MinValue, MaxValue, AllowNaN, AllowInfinity) - Lists of Double
  • ListOfBooleans(MinCount, MaxCount) - Lists of Boolean
  • ListOf(MinCount, MaxCount, ElementGenerator, ElementTypeInfo) - Generic list generator
procedureTTests.RunTestListContains;
begin
THypothesis.Run(Self, 'TestListContains', [
THypothesis.ListOfIntegers(5, 10, 1, 100)
], 50);
end;
procedureTTests.TestListContains(const Values: IList<Int64>);
beginif Values.Count > 0then
Assert.IsTrue(Values.Contains(Values[0]));
end;

Dictionary Helper Methods

Dictionaries use Spring4D's IDictionary<K,V> interface:

  • DictIntegerToString(MinCount, MaxCount, KeyMin, KeyMax, ValueMinLen, ValueMaxLen, ValueCharSet) - Int64 → string
  • DictStringToInteger(MinCount, MaxCount, KeyMinLen, KeyMaxLen, KeyCharSet, ValueMin, ValueMax) - string → Int64
  • DictStringToString(MinCount, MaxCount, KeyMinLen, KeyMaxLen, KeyCharSet, ValueMinLen, ValueMaxLen, ValueCharSet) - string → string
  • DictOf(MinCount, MaxCount, KeyGenerator, ValueGenerator, KeyTypeInfo, ValueTypeInfo) - Generic dictionary generator
// Generate dictionary with 3-8 entries, Int64 keys (1-100), string values (5-15 chars)
THypothesis.DictIntegerToString(3, 8, 1, 100, 5, 15, TStringCharSet.Alpha)

Multiple Collection Parameters

You can pass multiple generators to test interactions between collections:

procedureTTests.RunTestArrayConcatenation;
begin
THypothesis.Run(Self, 'TestArrayConcatenation', [
THypothesis.ArrayOfIntegers(3, 5, 1, 100),
THypothesis.ArrayOfIntegers(3, 5, 1, 100)
], 50);
end;
procedureTTests.TestArrayConcatenation(const A1, A2: TArray<Int64>);
var
Combined: TArray<Int64>;
begin
SetLength(Combined, Length(A1) + Length(A2));
// ... concatenation logic ...
Assert.AreEqual(Length(A1) + Length(A2), Length(Combined));
end;

Configuring Iterations

Use the ForAll attribute to specify the number of test iterations (default: 10).

[ForAll(1000)]
procedureTestWithManyIterations([IntRange(1, 100)] constValue: Integer);

Multiple Parameters

Property tests can accept multiple parameters with different strategies.

Using Attribute-Based Generation

[ForAll(100)]
procedureTestAddition([IntRange(-1000, 1000)] const A: Integer;
[IntRange(-1000, 1000)] const B: Integer);
beginconst Sum = Int64(A) + Int64(B);
Assert.AreEqual(Sum, Int64(B) + Int64(A), 'Addition should be commutative');
end;

Using Generator-Based Execution

For more complex scenarios or when using collections, you can pass generators directly:

[Test]
procedureRunTestComplexScenario;
procedureTestComplexScenario(const Values: TArray<Int64>; constName: string);
// ImplementationprocedureTTests.RunTestComplexScenario;
begin
THypothesis.Run(Self, 'TestComplexScenario', [
THypothesis.ArrayOfIntegers(5, 10, 1, 100),
THypothesis.StringAlpha(3, 20) // Note: StringAlpha not yet implemented as helper
], 50);
end;

Note: Currently, only collection helpers are available. String and other type helpers are planned for future versions.

How Shrinking Works

When a test fails, Hypothesis automatically searches for a simpler failing example:

  • Integers: Binary search towards zero within valid range
  • Strings: Reduce length and simplify characters
  • Booleans: True shrinks to False
  • Floats: Special values → zero → nearest integer → halfway towards zero
  • Dates: Year shrinks towards 2000, month towards January, day towards 1st
  • DateTimes: Date components shrink as above, time shrinks towards midnight
  • Times: Hour/minute/second/millisecond each shrink towards zero

Example failure output:

Property test failed on iteration 47/100
Original values: 'AbCdEfGhIjKlMnOp', 42
Minimal failing example: 'A', 1
Seed: 12345678

Best Practices

  1. Keep properties simple: Each test should verify one property
  2. Use appropriate ranges: Don't generate more values than needed
  3. Handle edge cases: Consider empty strings, zero, negative numbers
  4. Use meaningful parameter names: The ParamName helps in error messages
  5. Start with fewer iterations: Use 10-100 iterations during development, increase for CI

Examples

The project includes two types of examples:

Example Code

Delphi.Source/examples/Hypothesis.Examples.pas - Simple utility classes demonstrating testable code:

  • TStringUtils: String manipulation functions
  • TMathUtils: Mathematical operations

Example Tests

Delphi.Source/examples/Hypothesis.Examples.Tests.pas - Property-based tests for the example code:

  • String reversal properties (involutive, length preservation)
  • Arithmetic properties (commutativity, associativity)
  • Consistency checks

Framework Tests

Delphi.Source/tests/Hypothesis.Core.Tests.pas - Comprehensive test suite including:

  • Integer properties (reverse, addition, absolute value)
  • String properties (reverse, concatenation, uppercase)
  • Strategy validation (positive, negative, non-zero, alpha, numeric)
  • Combined integer and string properties

Architecture

The library consists of the following main components:

  1. Hypothesis.Attributes: Custom attributes for declaring strategies (ForAll, IntRange, StringAlpha, etc.)
  2. Hypothesis.Generators.*: Value generators with shrinking logic
    • Hypothesis.Generators.Interfaces: Base interface for all generators
    • Hypothesis.Generators.Integers: Integer value generation and shrinking
    • Hypothesis.Generators.Strings: String value generation and shrinking
    • Hypothesis.Generators.Factory: Creates appropriate generators from attributes
  3. Hypothesis.Core: Test runner with RTTI-based parameter inspection and shrinking orchestration
  4. Hypothesis.Runner: DUnitX framework integration helper (THypothesis.Run)
  5. Hypothesis.Exceptions: Custom exception types for property test failures

Limitations

Current implementation includes:

✅ Supported Types:

  • Integers (Int64)
  • Strings (with multiple character sets)
  • Booleans
  • Floats/Doubles (with special values)
  • Dates, DateTimes, and Times
  • Collections (TArray, IList, IDictionary via helper methods)

⚠️ Collection Limitations:

  • Collections require manual instantiation using helper methods
  • Cannot use attribute-based generation for collections (Delphi language limitation)
  • Collection types are limited to: Int64, string, Double, Boolean

📋 Not Yet Implemented:

  • Records and custom object types
  • Advanced shrinking strategies
  • Database/persistence for test cases
  • Stateful testing
  • Custom strategy composition
  • Full generic collection support

Future versions may add support for records, advanced shrinking, and more flexible collection generators.

Troubleshooting

"Parameter has no strategy attribute"

Ensure all test method parameters have a strategy attribute (e.g., IntRange, StringAlpha).

"Unsupported attribute type"

Check that you're using one of the supported strategy attributes.

Tests are too slow

Reduce the number of iterations in the ForAll attribute or narrow the value ranges.

Contributing

This is an experimental library. Feedback and contributions are welcome.

License

[Specify your license here]

Acknowledgments

Inspired by the excellent Hypothesis library for Python.

About

Delphi implementation for the Python Hyopthesis library. Meant for unit testing strategies.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - GDKsoftware/Hypothesis4D: Delphi implementation for the Python Hyopthesis library. Meant for unit testing strategies. · GitHub
Skip to content

Repository files navigation

Hypothesis for Delphi

A minimal property-based testing library for Delphi, inspired by Python's Hypothesis library.

Overview

Hypothesis for Delphi enables property-based testing using custom attributes and automatic value generation with shrinking support. Instead of writing individual test cases with specific values, you define properties that should hold true for all valid inputs, and Hypothesis generates test data automatically.

Property-Based Testing vs Traditional Testing

Traditional unit tests verify behavior with specific, hand-picked examples: "when I reverse 'hello', I get 'olleh'". Property-based testing takes a different approach by defining general rules: "reversing any string twice returns the original". Hypothesis then automatically generates hundreds of test cases to verify this property holds for empty strings, single characters, long strings, special characters, and edge cases you might not have thought of. When a property fails, Hypothesis automatically "shrinks" the failing input to find the simplest example that breaks your code, making debugging much easier. This approach catches corner cases that manual testing often misses, while requiring less test code to write and maintain. Property-based testing complements traditional example-based tests perfectly: use examples for specific known scenarios and edge cases, and use properties to verify general behavior across a wide range of inputs.

Features

  • Automatic Value Generation: Generate random test data based on declarative strategies
  • Smart Shrinking: When a test fails, automatically finds the minimal failing example
  • DUnitX Integration: Seamless integration with the DUnitX test framework
  • Type-Safe: Leverages Delphi's RTTI and generics for type safety
  • Configurable: Control iteration counts and random seeds for reproducibility

Requirements

  • Delphi 11 (or newer)
  • DUnitX test framework
  • Spring4D collections library

Installation

  1. Add the Delphi.Source\src folder to your project's search path
  2. Ensure DUnitX and Spring4D are available in your project
  3. Add the required units to your test project:
    • Hypothesis.Attributes - Strategy attribute declarations
    • Hypothesis.Runner - Test execution helper
    • Hypothesis.Core - Core test runner (automatically included)
    • Hypothesis.Generators.* - Value generators (automatically included)

Quick Start

unit MyTests;
interfaceuses
DUnitX.TestFramework,
Hypothesis.Attributes,
Hypothesis.Runner;
type
[TestFixture]
TMyPropertyTests = classpublic
[Test]
procedureRunTestStringReverse;
[ForAll(100)]
procedureTestStringReverse([StringAlpha(0, 50)] const Text: string);
end;
implementationuses
System.SysUtils;
functionReverseString(const S: string): string;
begin
Result := '';
forvar I := S.Length downto1do
Result := Result + S[I];
end;
procedureTMyPropertyTests.RunTestStringReverse;
begin
THypothesis.Run(Self, 'TestStringReverse');
end;
procedureTMyPropertyTests.TestStringReverse(const Text: string);
beginvar Reversed := ReverseString(Text);
var DoubleReversed := ReverseString(Reversed);
Assert.AreEqual(Text, DoubleReversed,
'Reversing a string twice should give the original string');
end;
end.

Strategy Attributes

Integer Strategies

IntRange(Min, Max)

Generates integers within the specified range (inclusive).

[Test]
procedureRunTestIntRange;
[ForAll(100)]
procedureTestIntRange([IntRange(-100, 100)] constValue: Integer);
// ImplementationprocedureTMyTests.RunTestIntRange;
begin
THypothesis.Run(Self, 'TestIntRange');
end;

IntPositive(Max)

Generates positive integers from 1 to Max (inclusive).

[ForAll(100)]
procedureTestPositive([IntPositive(1000)] const Count: Integer);

IntNegative(Min)

Generates negative integers from Min to -1 (inclusive).

[ForAll(100)]
procedureTestNegative([IntNegative(-1000)] const Debt: Integer);

IntNonZero(Min, Max)

Generates integers in the range Min to Max, excluding zero.

[ForAll(100)]
procedureTestNonZero([IntNonZero(-100, 100)] const Divisor: Integer);

String Strategies

StringGen(MinLen, MaxLen)

Generates strings with arbitrary printable characters.

[ForAll(100)]
procedureTestAnyString([StringGen(0, 100)] const Text: string);

StringAlpha(MinLen, MaxLen)

Generates strings containing only alphabetic characters (A-Z, a-z).

[ForAll(100)]
procedureTestAlphaString([StringAlpha(1, 50)] constName: string);

StringNumeric(MinLen, MaxLen)

Generates strings containing only numeric digits (0-9).

[ForAll(100)]
procedureTestNumericString([StringNumeric(5, 10)] const Code: string);

Boolean Strategies

Boolean

Generates boolean values (True or False).

[ForAll(100)]
procedureTestBoolean([Boolean] const Flag: Boolean);

Float/Double Strategies

FloatRange(Min, Max, AllowNaN, AllowInfinity)

Generates floating-point values within the specified range.

[ForAll(100)]
procedureTestFloatRange([FloatRange(-100.0, 100.0)] constValue: Double);
// With special values
[ForAll(100)]
procedureTestFloatSpecial([FloatRange(-10.0, 10.0, True, True)] constValue: Double);

FloatPositive(Max)

Generates positive floating-point values greater than zero.

[ForAll(100)]
procedureTestPositiveFloat([FloatPositive(1000.0)] const Amount: Double);

FloatNegative(Min)

Generates negative floating-point values less than zero.

[ForAll(100)]
procedureTestNegativeFloat([FloatNegative(-1000.0)] const Debt: Double);

FloatUnit

Generates floating-point values in the unit interval [0.0, 1.0].

[ForAll(100)]
procedureTestProbability([FloatUnit] const Probability: Double);

Date/DateTime Strategies

DateRange(MinYear, MaxYear)

Generates date values (TDate) within the specified year range.

[ForAll(100)]
procedureTestDate([DateRange(1900, 2100)] const Date: TDate);

DateTimeRange(MinYear, MaxYear)

Generates datetime values (TDateTime) with both date and time components.

[ForAll(100)]
procedureTestDateTime([DateTimeRange(1900, 2100)] const DT: TDateTime);

DateRecent(Days)

Generates recent dates within the specified number of days from today.

[ForAll(100)]
procedureTestRecentDate([DateRecent(30)] const Date: TDate);

TimeRange

Generates time values (TTime) representing time of day (00:00:00 to 23:59:59).

[ForAll(100)]
procedureTestTime([TimeRange] const Time: TTime);

Collection Strategies

Collection generators (arrays, lists, dictionaries) require manual instantiation due to Delphi's compile-time constant requirements in attributes. Hypothesis provides convenient helper methods to simplify collection generation.

Using Collection Helpers

Instead of using attributes, use the helper methods with the generator-based Run overload:

type
[TestFixture]
TCollectionTests = classpublic
[Test]
procedureRunTestArraySum;
procedureTestArraySum(const Values: TArray<Int64>);
end;
implementationprocedureTCollectionTests.RunTestArraySum;
begin
THypothesis.Run(Self, 'TestArraySum', [
THypothesis.ArrayOfIntegers(5, 10, 1, 100)
], 50);
end;
procedureTCollectionTests.TestArraySum(const Values: TArray<Int64>);
var
Sum: Int64;
I: Integer;
begin
Sum := 0;
for I := 0to High(Values) do
Sum := Sum + Values[I];
Assert.IsTrue(Sum > 0, 'Sum of positive integers should be positive');
Assert.IsTrue(Length(Values) >= 5);
Assert.IsTrue(Length(Values) <= 10);
end;

Array Helper Methods

  • ArrayOfIntegers(MinCount, MaxCount, MinValue, MaxValue, ExcludeZero) - Arrays of Int64
  • ArrayOfStrings(MinCount, MaxCount, MinLen, MaxLen, CharSet) - Arrays of strings
  • ArrayOfFloats(MinCount, MaxCount, MinValue, MaxValue, AllowNaN, AllowInfinity) - Arrays of Double
  • ArrayOfBooleans(MinCount, MaxCount) - Arrays of Boolean
  • ArrayOf(MinCount, MaxCount, ElementGenerator, ElementTypeInfo) - Generic array generator
// Generate array of 3-7 strings, each 5-15 characters long
THypothesis.ArrayOfStrings(3, 7, 5, 15, TStringCharSet.Alpha)
// Generate array of 10-20 floats between -1.0 and 1.0
THypothesis.ArrayOfFloats(10, 20, -1.0, 1.0)

List Helper Methods

Lists use Spring4D's IList<T> interface:

  • ListOfIntegers(MinCount, MaxCount, MinValue, MaxValue, ExcludeZero) - Lists of Int64
  • ListOfStrings(MinCount, MaxCount, MinLen, MaxLen, CharSet) - Lists of strings
  • ListOfFloats(MinCount, MaxCount, MinValue, MaxValue, AllowNaN, AllowInfinity) - Lists of Double
  • ListOfBooleans(MinCount, MaxCount) - Lists of Boolean
  • ListOf(MinCount, MaxCount, ElementGenerator, ElementTypeInfo) - Generic list generator
procedureTTests.RunTestListContains;
begin
THypothesis.Run(Self, 'TestListContains', [
THypothesis.ListOfIntegers(5, 10, 1, 100)
], 50);
end;
procedureTTests.TestListContains(const Values: IList<Int64>);
beginif Values.Count > 0then
Assert.IsTrue(Values.Contains(Values[0]));
end;

Dictionary Helper Methods

Dictionaries use Spring4D's IDictionary<K,V> interface:

  • DictIntegerToString(MinCount, MaxCount, KeyMin, KeyMax, ValueMinLen, ValueMaxLen, ValueCharSet) - Int64 → string
  • DictStringToInteger(MinCount, MaxCount, KeyMinLen, KeyMaxLen, KeyCharSet, ValueMin, ValueMax) - string → Int64
  • DictStringToString(MinCount, MaxCount, KeyMinLen, KeyMaxLen, KeyCharSet, ValueMinLen, ValueMaxLen, ValueCharSet) - string → string
  • DictOf(MinCount, MaxCount, KeyGenerator, ValueGenerator, KeyTypeInfo, ValueTypeInfo) - Generic dictionary generator
// Generate dictionary with 3-8 entries, Int64 keys (1-100), string values (5-15 chars)
THypothesis.DictIntegerToString(3, 8, 1, 100, 5, 15, TStringCharSet.Alpha)

Multiple Collection Parameters

You can pass multiple generators to test interactions between collections:

procedureTTests.RunTestArrayConcatenation;
begin
THypothesis.Run(Self, 'TestArrayConcatenation', [
THypothesis.ArrayOfIntegers(3, 5, 1, 100),
THypothesis.ArrayOfIntegers(3, 5, 1, 100)
], 50);
end;
procedureTTests.TestArrayConcatenation(const A1, A2: TArray<Int64>);
var
Combined: TArray<Int64>;
begin
SetLength(Combined, Length(A1) + Length(A2));
// ... concatenation logic ...
Assert.AreEqual(Length(A1) + Length(A2), Length(Combined));
end;

Configuring Iterations

Use the ForAll attribute to specify the number of test iterations (default: 10).

[ForAll(1000)]
procedureTestWithManyIterations([IntRange(1, 100)] constValue: Integer);

Multiple Parameters

Property tests can accept multiple parameters with different strategies.

Using Attribute-Based Generation

[ForAll(100)]
procedureTestAddition([IntRange(-1000, 1000)] const A: Integer;
[IntRange(-1000, 1000)] const B: Integer);
beginconst Sum = Int64(A) + Int64(B);
Assert.AreEqual(Sum, Int64(B) + Int64(A), 'Addition should be commutative');
end;

Using Generator-Based Execution

For more complex scenarios or when using collections, you can pass generators directly:

[Test]
procedureRunTestComplexScenario;
procedureTestComplexScenario(const Values: TArray<Int64>; constName: string);
// ImplementationprocedureTTests.RunTestComplexScenario;
begin
THypothesis.Run(Self, 'TestComplexScenario', [
THypothesis.ArrayOfIntegers(5, 10, 1, 100),
THypothesis.StringAlpha(3, 20) // Note: StringAlpha not yet implemented as helper
], 50);
end;

Note: Currently, only collection helpers are available. String and other type helpers are planned for future versions.

How Shrinking Works

When a test fails, Hypothesis automatically searches for a simpler failing example:

  • Integers: Binary search towards zero within valid range
  • Strings: Reduce length and simplify characters
  • Booleans: True shrinks to False
  • Floats: Special values → zero → nearest integer → halfway towards zero
  • Dates: Year shrinks towards 2000, month towards January, day towards 1st
  • DateTimes: Date components shrink as above, time shrinks towards midnight
  • Times: Hour/minute/second/millisecond each shrink towards zero

Example failure output:

Property test failed on iteration 47/100
Original values: 'AbCdEfGhIjKlMnOp', 42
Minimal failing example: 'A', 1
Seed: 12345678

Best Practices

  1. Keep properties simple: Each test should verify one property
  2. Use appropriate ranges: Don't generate more values than needed
  3. Handle edge cases: Consider empty strings, zero, negative numbers
  4. Use meaningful parameter names: The ParamName helps in error messages
  5. Start with fewer iterations: Use 10-100 iterations during development, increase for CI

Examples

The project includes two types of examples:

Example Code

Delphi.Source/examples/Hypothesis.Examples.pas - Simple utility classes demonstrating testable code:

  • TStringUtils: String manipulation functions
  • TMathUtils: Mathematical operations

Example Tests

Delphi.Source/examples/Hypothesis.Examples.Tests.pas - Property-based tests for the example code:

  • String reversal properties (involutive, length preservation)
  • Arithmetic properties (commutativity, associativity)
  • Consistency checks

Framework Tests

Delphi.Source/tests/Hypothesis.Core.Tests.pas - Comprehensive test suite including:

  • Integer properties (reverse, addition, absolute value)
  • String properties (reverse, concatenation, uppercase)
  • Strategy validation (positive, negative, non-zero, alpha, numeric)
  • Combined integer and string properties

Architecture

The library consists of the following main components:

  1. Hypothesis.Attributes: Custom attributes for declaring strategies (ForAll, IntRange, StringAlpha, etc.)
  2. Hypothesis.Generators.*: Value generators with shrinking logic
    • Hypothesis.Generators.Interfaces: Base interface for all generators
    • Hypothesis.Generators.Integers: Integer value generation and shrinking
    • Hypothesis.Generators.Strings: String value generation and shrinking
    • Hypothesis.Generators.Factory: Creates appropriate generators from attributes
  3. Hypothesis.Core: Test runner with RTTI-based parameter inspection and shrinking orchestration
  4. Hypothesis.Runner: DUnitX framework integration helper (THypothesis.Run)
  5. Hypothesis.Exceptions: Custom exception types for property test failures

Limitations

Current implementation includes:

✅ Supported Types:

  • Integers (Int64)
  • Strings (with multiple character sets)
  • Booleans
  • Floats/Doubles (with special values)
  • Dates, DateTimes, and Times
  • Collections (TArray, IList, IDictionary via helper methods)

⚠️ Collection Limitations:

  • Collections require manual instantiation using helper methods
  • Cannot use attribute-based generation for collections (Delphi language limitation)
  • Collection types are limited to: Int64, string, Double, Boolean

📋 Not Yet Implemented:

  • Records and custom object types
  • Advanced shrinking strategies
  • Database/persistence for test cases
  • Stateful testing
  • Custom strategy composition
  • Full generic collection support

Future versions may add support for records, advanced shrinking, and more flexible collection generators.

Troubleshooting

"Parameter has no strategy attribute"

Ensure all test method parameters have a strategy attribute (e.g., IntRange, StringAlpha).

"Unsupported attribute type"

Check that you're using one of the supported strategy attributes.

Tests are too slow

Reduce the number of iterations in the ForAll attribute or narrow the value ranges.

Contributing

This is an experimental library. Feedback and contributions are welcome.

License

[Specify your license here]

Acknowledgments

Inspired by the excellent Hypothesis library for Python.

About

Delphi implementation for the Python Hyopthesis library. Meant for unit testing strategies.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - GDKsoftware/Hypothesis4D: Delphi implementation for the Python Hyopthesis library. Meant for unit testing strategies. · GitHub
Skip to content

Repository files navigation

Hypothesis for Delphi

A minimal property-based testing library for Delphi, inspired by Python's Hypothesis library.

Overview

Hypothesis for Delphi enables property-based testing using custom attributes and automatic value generation with shrinking support. Instead of writing individual test cases with specific values, you define properties that should hold true for all valid inputs, and Hypothesis generates test data automatically.

Property-Based Testing vs Traditional Testing

Traditional unit tests verify behavior with specific, hand-picked examples: "when I reverse 'hello', I get 'olleh'". Property-based testing takes a different approach by defining general rules: "reversing any string twice returns the original". Hypothesis then automatically generates hundreds of test cases to verify this property holds for empty strings, single characters, long strings, special characters, and edge cases you might not have thought of. When a property fails, Hypothesis automatically "shrinks" the failing input to find the simplest example that breaks your code, making debugging much easier. This approach catches corner cases that manual testing often misses, while requiring less test code to write and maintain. Property-based testing complements traditional example-based tests perfectly: use examples for specific known scenarios and edge cases, and use properties to verify general behavior across a wide range of inputs.

Features

  • Automatic Value Generation: Generate random test data based on declarative strategies
  • Smart Shrinking: When a test fails, automatically finds the minimal failing example
  • DUnitX Integration: Seamless integration with the DUnitX test framework
  • Type-Safe: Leverages Delphi's RTTI and generics for type safety
  • Configurable: Control iteration counts and random seeds for reproducibility

Requirements

  • Delphi 11 (or newer)
  • DUnitX test framework
  • Spring4D collections library

Installation

  1. Add the Delphi.Source\src folder to your project's search path
  2. Ensure DUnitX and Spring4D are available in your project
  3. Add the required units to your test project:
    • Hypothesis.Attributes - Strategy attribute declarations
    • Hypothesis.Runner - Test execution helper
    • Hypothesis.Core - Core test runner (automatically included)
    • Hypothesis.Generators.* - Value generators (automatically included)

Quick Start

unit MyTests;
interfaceuses
DUnitX.TestFramework,
Hypothesis.Attributes,
Hypothesis.Runner;
type
[TestFixture]
TMyPropertyTests = classpublic
[Test]
procedureRunTestStringReverse;
[ForAll(100)]
procedureTestStringReverse([StringAlpha(0, 50)] const Text: string);
end;
implementationuses
System.SysUtils;
functionReverseString(const S: string): string;
begin
Result := '';
forvar I := S.Length downto1do
Result := Result + S[I];
end;
procedureTMyPropertyTests.RunTestStringReverse;
begin
THypothesis.Run(Self, 'TestStringReverse');
end;
procedureTMyPropertyTests.TestStringReverse(const Text: string);
beginvar Reversed := ReverseString(Text);
var DoubleReversed := ReverseString(Reversed);
Assert.AreEqual(Text, DoubleReversed,
'Reversing a string twice should give the original string');
end;
end.

Strategy Attributes

Integer Strategies

IntRange(Min, Max)

Generates integers within the specified range (inclusive).

[Test]
procedureRunTestIntRange;
[ForAll(100)]
procedureTestIntRange([IntRange(-100, 100)] constValue: Integer);
// ImplementationprocedureTMyTests.RunTestIntRange;
begin
THypothesis.Run(Self, 'TestIntRange');
end;

IntPositive(Max)

Generates positive integers from 1 to Max (inclusive).

[ForAll(100)]
procedureTestPositive([IntPositive(1000)] const Count: Integer);

IntNegative(Min)

Generates negative integers from Min to -1 (inclusive).

[ForAll(100)]
procedureTestNegative([IntNegative(-1000)] const Debt: Integer);

IntNonZero(Min, Max)

Generates integers in the range Min to Max, excluding zero.

[ForAll(100)]
procedureTestNonZero([IntNonZero(-100, 100)] const Divisor: Integer);

String Strategies

StringGen(MinLen, MaxLen)

Generates strings with arbitrary printable characters.

[ForAll(100)]
procedureTestAnyString([StringGen(0, 100)] const Text: string);

StringAlpha(MinLen, MaxLen)

Generates strings containing only alphabetic characters (A-Z, a-z).

[ForAll(100)]
procedureTestAlphaString([StringAlpha(1, 50)] constName: string);

StringNumeric(MinLen, MaxLen)

Generates strings containing only numeric digits (0-9).

[ForAll(100)]
procedureTestNumericString([StringNumeric(5, 10)] const Code: string);

Boolean Strategies

Boolean

Generates boolean values (True or False).

[ForAll(100)]
procedureTestBoolean([Boolean] const Flag: Boolean);

Float/Double Strategies

FloatRange(Min, Max, AllowNaN, AllowInfinity)

Generates floating-point values within the specified range.

[ForAll(100)]
procedureTestFloatRange([FloatRange(-100.0, 100.0)] constValue: Double);
// With special values
[ForAll(100)]
procedureTestFloatSpecial([FloatRange(-10.0, 10.0, True, True)] constValue: Double);

FloatPositive(Max)

Generates positive floating-point values greater than zero.

[ForAll(100)]
procedureTestPositiveFloat([FloatPositive(1000.0)] const Amount: Double);

FloatNegative(Min)

Generates negative floating-point values less than zero.

[ForAll(100)]
procedureTestNegativeFloat([FloatNegative(-1000.0)] const Debt: Double);

FloatUnit

Generates floating-point values in the unit interval [0.0, 1.0].

[ForAll(100)]
procedureTestProbability([FloatUnit] const Probability: Double);

Date/DateTime Strategies

DateRange(MinYear, MaxYear)

Generates date values (TDate) within the specified year range.

[ForAll(100)]
procedureTestDate([DateRange(1900, 2100)] const Date: TDate);

DateTimeRange(MinYear, MaxYear)

Generates datetime values (TDateTime) with both date and time components.

[ForAll(100)]
procedureTestDateTime([DateTimeRange(1900, 2100)] const DT: TDateTime);

DateRecent(Days)

Generates recent dates within the specified number of days from today.

[ForAll(100)]
procedureTestRecentDate([DateRecent(30)] const Date: TDate);

TimeRange

Generates time values (TTime) representing time of day (00:00:00 to 23:59:59).

[ForAll(100)]
procedureTestTime([TimeRange] const Time: TTime);

Collection Strategies

Collection generators (arrays, lists, dictionaries) require manual instantiation due to Delphi's compile-time constant requirements in attributes. Hypothesis provides convenient helper methods to simplify collection generation.

Using Collection Helpers

Instead of using attributes, use the helper methods with the generator-based Run overload:

type
[TestFixture]
TCollectionTests = classpublic
[Test]
procedureRunTestArraySum;
procedureTestArraySum(const Values: TArray<Int64>);
end;
implementationprocedureTCollectionTests.RunTestArraySum;
begin
THypothesis.Run(Self, 'TestArraySum', [
THypothesis.ArrayOfIntegers(5, 10, 1, 100)
], 50);
end;
procedureTCollectionTests.TestArraySum(const Values: TArray<Int64>);
var
Sum: Int64;
I: Integer;
begin
Sum := 0;
for I := 0to High(Values) do
Sum := Sum + Values[I];
Assert.IsTrue(Sum > 0, 'Sum of positive integers should be positive');
Assert.IsTrue(Length(Values) >= 5);
Assert.IsTrue(Length(Values) <= 10);
end;

Array Helper Methods

  • ArrayOfIntegers(MinCount, MaxCount, MinValue, MaxValue, ExcludeZero) - Arrays of Int64
  • ArrayOfStrings(MinCount, MaxCount, MinLen, MaxLen, CharSet) - Arrays of strings
  • ArrayOfFloats(MinCount, MaxCount, MinValue, MaxValue, AllowNaN, AllowInfinity) - Arrays of Double
  • ArrayOfBooleans(MinCount, MaxCount) - Arrays of Boolean
  • ArrayOf(MinCount, MaxCount, ElementGenerator, ElementTypeInfo) - Generic array generator
// Generate array of 3-7 strings, each 5-15 characters long
THypothesis.ArrayOfStrings(3, 7, 5, 15, TStringCharSet.Alpha)
// Generate array of 10-20 floats between -1.0 and 1.0
THypothesis.ArrayOfFloats(10, 20, -1.0, 1.0)

List Helper Methods

Lists use Spring4D's IList<T> interface:

  • ListOfIntegers(MinCount, MaxCount, MinValue, MaxValue, ExcludeZero) - Lists of Int64
  • ListOfStrings(MinCount, MaxCount, MinLen, MaxLen, CharSet) - Lists of strings
  • ListOfFloats(MinCount, MaxCount, MinValue, MaxValue, AllowNaN, AllowInfinity) - Lists of Double
  • ListOfBooleans(MinCount, MaxCount) - Lists of Boolean
  • ListOf(MinCount, MaxCount, ElementGenerator, ElementTypeInfo) - Generic list generator
procedureTTests.RunTestListContains;
begin
THypothesis.Run(Self, 'TestListContains', [
THypothesis.ListOfIntegers(5, 10, 1, 100)
], 50);
end;
procedureTTests.TestListContains(const Values: IList<Int64>);
beginif Values.Count > 0then
Assert.IsTrue(Values.Contains(Values[0]));
end;

Dictionary Helper Methods

Dictionaries use Spring4D's IDictionary<K,V> interface:

  • DictIntegerToString(MinCount, MaxCount, KeyMin, KeyMax, ValueMinLen, ValueMaxLen, ValueCharSet) - Int64 → string
  • DictStringToInteger(MinCount, MaxCount, KeyMinLen, KeyMaxLen, KeyCharSet, ValueMin, ValueMax) - string → Int64
  • DictStringToString(MinCount, MaxCount, KeyMinLen, KeyMaxLen, KeyCharSet, ValueMinLen, ValueMaxLen, ValueCharSet) - string → string
  • DictOf(MinCount, MaxCount, KeyGenerator, ValueGenerator, KeyTypeInfo, ValueTypeInfo) - Generic dictionary generator
// Generate dictionary with 3-8 entries, Int64 keys (1-100), string values (5-15 chars)
THypothesis.DictIntegerToString(3, 8, 1, 100, 5, 15, TStringCharSet.Alpha)

Multiple Collection Parameters

You can pass multiple generators to test interactions between collections:

procedureTTests.RunTestArrayConcatenation;
begin
THypothesis.Run(Self, 'TestArrayConcatenation', [
THypothesis.ArrayOfIntegers(3, 5, 1, 100),
THypothesis.ArrayOfIntegers(3, 5, 1, 100)
], 50);
end;
procedureTTests.TestArrayConcatenation(const A1, A2: TArray<Int64>);
var
Combined: TArray<Int64>;
begin
SetLength(Combined, Length(A1) + Length(A2));
// ... concatenation logic ...
Assert.AreEqual(Length(A1) + Length(A2), Length(Combined));
end;

Configuring Iterations

Use the ForAll attribute to specify the number of test iterations (default: 10).

[ForAll(1000)]
procedureTestWithManyIterations([IntRange(1, 100)] constValue: Integer);

Multiple Parameters

Property tests can accept multiple parameters with different strategies.

Using Attribute-Based Generation

[ForAll(100)]
procedureTestAddition([IntRange(-1000, 1000)] const A: Integer;
[IntRange(-1000, 1000)] const B: Integer);
beginconst Sum = Int64(A) + Int64(B);
Assert.AreEqual(Sum, Int64(B) + Int64(A), 'Addition should be commutative');
end;

Using Generator-Based Execution

For more complex scenarios or when using collections, you can pass generators directly:

[Test]
procedureRunTestComplexScenario;
procedureTestComplexScenario(const Values: TArray<Int64>; constName: string);
// ImplementationprocedureTTests.RunTestComplexScenario;
begin
THypothesis.Run(Self, 'TestComplexScenario', [
THypothesis.ArrayOfIntegers(5, 10, 1, 100),
THypothesis.StringAlpha(3, 20) // Note: StringAlpha not yet implemented as helper
], 50);
end;

Note: Currently, only collection helpers are available. String and other type helpers are planned for future versions.

How Shrinking Works

When a test fails, Hypothesis automatically searches for a simpler failing example:

  • Integers: Binary search towards zero within valid range
  • Strings: Reduce length and simplify characters
  • Booleans: True shrinks to False
  • Floats: Special values → zero → nearest integer → halfway towards zero
  • Dates: Year shrinks towards 2000, month towards January, day towards 1st
  • DateTimes: Date components shrink as above, time shrinks towards midnight
  • Times: Hour/minute/second/millisecond each shrink towards zero

Example failure output:

Property test failed on iteration 47/100
Original values: 'AbCdEfGhIjKlMnOp', 42
Minimal failing example: 'A', 1
Seed: 12345678

Best Practices

  1. Keep properties simple: Each test should verify one property
  2. Use appropriate ranges: Don't generate more values than needed
  3. Handle edge cases: Consider empty strings, zero, negative numbers
  4. Use meaningful parameter names: The ParamName helps in error messages
  5. Start with fewer iterations: Use 10-100 iterations during development, increase for CI

Examples

The project includes two types of examples:

Example Code

Delphi.Source/examples/Hypothesis.Examples.pas - Simple utility classes demonstrating testable code:

  • TStringUtils: String manipulation functions
  • TMathUtils: Mathematical operations

Example Tests

Delphi.Source/examples/Hypothesis.Examples.Tests.pas - Property-based tests for the example code:

  • String reversal properties (involutive, length preservation)
  • Arithmetic properties (commutativity, associativity)
  • Consistency checks

Framework Tests

Delphi.Source/tests/Hypothesis.Core.Tests.pas - Comprehensive test suite including:

  • Integer properties (reverse, addition, absolute value)
  • String properties (reverse, concatenation, uppercase)
  • Strategy validation (positive, negative, non-zero, alpha, numeric)
  • Combined integer and string properties

Architecture

The library consists of the following main components:

  1. Hypothesis.Attributes: Custom attributes for declaring strategies (ForAll, IntRange, StringAlpha, etc.)
  2. Hypothesis.Generators.*: Value generators with shrinking logic
    • Hypothesis.Generators.Interfaces: Base interface for all generators
    • Hypothesis.Generators.Integers: Integer value generation and shrinking
    • Hypothesis.Generators.Strings: String value generation and shrinking
    • Hypothesis.Generators.Factory: Creates appropriate generators from attributes
  3. Hypothesis.Core: Test runner with RTTI-based parameter inspection and shrinking orchestration
  4. Hypothesis.Runner: DUnitX framework integration helper (THypothesis.Run)
  5. Hypothesis.Exceptions: Custom exception types for property test failures

Limitations

Current implementation includes:

✅ Supported Types:

  • Integers (Int64)
  • Strings (with multiple character sets)
  • Booleans
  • Floats/Doubles (with special values)
  • Dates, DateTimes, and Times
  • Collections (TArray, IList, IDictionary via helper methods)

⚠️ Collection Limitations:

  • Collections require manual instantiation using helper methods
  • Cannot use attribute-based generation for collections (Delphi language limitation)
  • Collection types are limited to: Int64, string, Double, Boolean

📋 Not Yet Implemented:

  • Records and custom object types
  • Advanced shrinking strategies
  • Database/persistence for test cases
  • Stateful testing
  • Custom strategy composition
  • Full generic collection support

Future versions may add support for records, advanced shrinking, and more flexible collection generators.

Troubleshooting

"Parameter has no strategy attribute"

Ensure all test method parameters have a strategy attribute (e.g., IntRange, StringAlpha).

"Unsupported attribute type"

Check that you're using one of the supported strategy attributes.

Tests are too slow

Reduce the number of iterations in the ForAll attribute or narrow the value ranges.

Contributing

This is an experimental library. Feedback and contributions are welcome.

License

[Specify your license here]

Acknowledgments

Inspired by the excellent Hypothesis library for Python.

About

Delphi implementation for the Python Hyopthesis library. Meant for unit testing strategies.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - GDKsoftware/Hypothesis4D: Delphi implementation for the Python Hyopthesis library. Meant for unit testing strategies. · GitHub
Skip to content

Repository files navigation

Hypothesis for Delphi

A minimal property-based testing library for Delphi, inspired by Python's Hypothesis library.

Overview

Hypothesis for Delphi enables property-based testing using custom attributes and automatic value generation with shrinking support. Instead of writing individual test cases with specific values, you define properties that should hold true for all valid inputs, and Hypothesis generates test data automatically.

Property-Based Testing vs Traditional Testing

Traditional unit tests verify behavior with specific, hand-picked examples: "when I reverse 'hello', I get 'olleh'". Property-based testing takes a different approach by defining general rules: "reversing any string twice returns the original". Hypothesis then automatically generates hundreds of test cases to verify this property holds for empty strings, single characters, long strings, special characters, and edge cases you might not have thought of. When a property fails, Hypothesis automatically "shrinks" the failing input to find the simplest example that breaks your code, making debugging much easier. This approach catches corner cases that manual testing often misses, while requiring less test code to write and maintain. Property-based testing complements traditional example-based tests perfectly: use examples for specific known scenarios and edge cases, and use properties to verify general behavior across a wide range of inputs.

Features

  • Automatic Value Generation: Generate random test data based on declarative strategies
  • Smart Shrinking: When a test fails, automatically finds the minimal failing example
  • DUnitX Integration: Seamless integration with the DUnitX test framework
  • Type-Safe: Leverages Delphi's RTTI and generics for type safety
  • Configurable: Control iteration counts and random seeds for reproducibility

Requirements

  • Delphi 11 (or newer)
  • DUnitX test framework
  • Spring4D collections library

Installation

  1. Add the Delphi.Source\src folder to your project's search path
  2. Ensure DUnitX and Spring4D are available in your project
  3. Add the required units to your test project:
    • Hypothesis.Attributes - Strategy attribute declarations
    • Hypothesis.Runner - Test execution helper
    • Hypothesis.Core - Core test runner (automatically included)
    • Hypothesis.Generators.* - Value generators (automatically included)

Quick Start

unit MyTests;
interfaceuses
DUnitX.TestFramework,
Hypothesis.Attributes,
Hypothesis.Runner;
type
[TestFixture]
TMyPropertyTests = classpublic
[Test]
procedureRunTestStringReverse;
[ForAll(100)]
procedureTestStringReverse([StringAlpha(0, 50)] const Text: string);
end;
implementationuses
System.SysUtils;
functionReverseString(const S: string): string;
begin
Result := '';
forvar I := S.Length downto1do
Result := Result + S[I];
end;
procedureTMyPropertyTests.RunTestStringReverse;
begin
THypothesis.Run(Self, 'TestStringReverse');
end;
procedureTMyPropertyTests.TestStringReverse(const Text: string);
beginvar Reversed := ReverseString(Text);
var DoubleReversed := ReverseString(Reversed);
Assert.AreEqual(Text, DoubleReversed,
'Reversing a string twice should give the original string');
end;
end.

Strategy Attributes

Integer Strategies

IntRange(Min, Max)

Generates integers within the specified range (inclusive).

[Test]
procedureRunTestIntRange;
[ForAll(100)]
procedureTestIntRange([IntRange(-100, 100)] constValue: Integer);
// ImplementationprocedureTMyTests.RunTestIntRange;
begin
THypothesis.Run(Self, 'TestIntRange');
end;

IntPositive(Max)

Generates positive integers from 1 to Max (inclusive).

[ForAll(100)]
procedureTestPositive([IntPositive(1000)] const Count: Integer);

IntNegative(Min)

Generates negative integers from Min to -1 (inclusive).

[ForAll(100)]
procedureTestNegative([IntNegative(-1000)] const Debt: Integer);

IntNonZero(Min, Max)

Generates integers in the range Min to Max, excluding zero.

[ForAll(100)]
procedureTestNonZero([IntNonZero(-100, 100)] const Divisor: Integer);

String Strategies

StringGen(MinLen, MaxLen)

Generates strings with arbitrary printable characters.

[ForAll(100)]
procedureTestAnyString([StringGen(0, 100)] const Text: string);

StringAlpha(MinLen, MaxLen)

Generates strings containing only alphabetic characters (A-Z, a-z).

[ForAll(100)]
procedureTestAlphaString([StringAlpha(1, 50)] constName: string);

StringNumeric(MinLen, MaxLen)

Generates strings containing only numeric digits (0-9).

[ForAll(100)]
procedureTestNumericString([StringNumeric(5, 10)] const Code: string);

Boolean Strategies

Boolean

Generates boolean values (True or False).

[ForAll(100)]
procedureTestBoolean([Boolean] const Flag: Boolean);

Float/Double Strategies

FloatRange(Min, Max, AllowNaN, AllowInfinity)

Generates floating-point values within the specified range.

[ForAll(100)]
procedureTestFloatRange([FloatRange(-100.0, 100.0)] constValue: Double);
// With special values
[ForAll(100)]
procedureTestFloatSpecial([FloatRange(-10.0, 10.0, True, True)] constValue: Double);

FloatPositive(Max)

Generates positive floating-point values greater than zero.

[ForAll(100)]
procedureTestPositiveFloat([FloatPositive(1000.0)] const Amount: Double);

FloatNegative(Min)

Generates negative floating-point values less than zero.

[ForAll(100)]
procedureTestNegativeFloat([FloatNegative(-1000.0)] const Debt: Double);

FloatUnit

Generates floating-point values in the unit interval [0.0, 1.0].

[ForAll(100)]
procedureTestProbability([FloatUnit] const Probability: Double);

Date/DateTime Strategies

DateRange(MinYear, MaxYear)

Generates date values (TDate) within the specified year range.

[ForAll(100)]
procedureTestDate([DateRange(1900, 2100)] const Date: TDate);

DateTimeRange(MinYear, MaxYear)

Generates datetime values (TDateTime) with both date and time components.

[ForAll(100)]
procedureTestDateTime([DateTimeRange(1900, 2100)] const DT: TDateTime);

DateRecent(Days)

Generates recent dates within the specified number of days from today.

[ForAll(100)]
procedureTestRecentDate([DateRecent(30)] const Date: TDate);

TimeRange

Generates time values (TTime) representing time of day (00:00:00 to 23:59:59).

[ForAll(100)]
procedureTestTime([TimeRange] const Time: TTime);

Collection Strategies

Collection generators (arrays, lists, dictionaries) require manual instantiation due to Delphi's compile-time constant requirements in attributes. Hypothesis provides convenient helper methods to simplify collection generation.

Using Collection Helpers

Instead of using attributes, use the helper methods with the generator-based Run overload:

type
[TestFixture]
TCollectionTests = classpublic
[Test]
procedureRunTestArraySum;
procedureTestArraySum(const Values: TArray<Int64>);
end;
implementationprocedureTCollectionTests.RunTestArraySum;
begin
THypothesis.Run(Self, 'TestArraySum', [
THypothesis.ArrayOfIntegers(5, 10, 1, 100)
], 50);
end;
procedureTCollectionTests.TestArraySum(const Values: TArray<Int64>);
var
Sum: Int64;
I: Integer;
begin
Sum := 0;
for I := 0to High(Values) do
Sum := Sum + Values[I];
Assert.IsTrue(Sum > 0, 'Sum of positive integers should be positive');
Assert.IsTrue(Length(Values) >= 5);
Assert.IsTrue(Length(Values) <= 10);
end;

Array Helper Methods

  • ArrayOfIntegers(MinCount, MaxCount, MinValue, MaxValue, ExcludeZero) - Arrays of Int64
  • ArrayOfStrings(MinCount, MaxCount, MinLen, MaxLen, CharSet) - Arrays of strings
  • ArrayOfFloats(MinCount, MaxCount, MinValue, MaxValue, AllowNaN, AllowInfinity) - Arrays of Double
  • ArrayOfBooleans(MinCount, MaxCount) - Arrays of Boolean
  • ArrayOf(MinCount, MaxCount, ElementGenerator, ElementTypeInfo) - Generic array generator
// Generate array of 3-7 strings, each 5-15 characters long
THypothesis.ArrayOfStrings(3, 7, 5, 15, TStringCharSet.Alpha)
// Generate array of 10-20 floats between -1.0 and 1.0
THypothesis.ArrayOfFloats(10, 20, -1.0, 1.0)

List Helper Methods

Lists use Spring4D's IList<T> interface:

  • ListOfIntegers(MinCount, MaxCount, MinValue, MaxValue, ExcludeZero) - Lists of Int64
  • ListOfStrings(MinCount, MaxCount, MinLen, MaxLen, CharSet) - Lists of strings
  • ListOfFloats(MinCount, MaxCount, MinValue, MaxValue, AllowNaN, AllowInfinity) - Lists of Double
  • ListOfBooleans(MinCount, MaxCount) - Lists of Boolean
  • ListOf(MinCount, MaxCount, ElementGenerator, ElementTypeInfo) - Generic list generator
procedureTTests.RunTestListContains;
begin
THypothesis.Run(Self, 'TestListContains', [
THypothesis.ListOfIntegers(5, 10, 1, 100)
], 50);
end;
procedureTTests.TestListContains(const Values: IList<Int64>);
beginif Values.Count > 0then
Assert.IsTrue(Values.Contains(Values[0]));
end;

Dictionary Helper Methods

Dictionaries use Spring4D's IDictionary<K,V> interface:

  • DictIntegerToString(MinCount, MaxCount, KeyMin, KeyMax, ValueMinLen, ValueMaxLen, ValueCharSet) - Int64 → string
  • DictStringToInteger(MinCount, MaxCount, KeyMinLen, KeyMaxLen, KeyCharSet, ValueMin, ValueMax) - string → Int64
  • DictStringToString(MinCount, MaxCount, KeyMinLen, KeyMaxLen, KeyCharSet, ValueMinLen, ValueMaxLen, ValueCharSet) - string → string
  • DictOf(MinCount, MaxCount, KeyGenerator, ValueGenerator, KeyTypeInfo, ValueTypeInfo) - Generic dictionary generator
// Generate dictionary with 3-8 entries, Int64 keys (1-100), string values (5-15 chars)
THypothesis.DictIntegerToString(3, 8, 1, 100, 5, 15, TStringCharSet.Alpha)

Multiple Collection Parameters

You can pass multiple generators to test interactions between collections:

procedureTTests.RunTestArrayConcatenation;
begin
THypothesis.Run(Self, 'TestArrayConcatenation', [
THypothesis.ArrayOfIntegers(3, 5, 1, 100),
THypothesis.ArrayOfIntegers(3, 5, 1, 100)
], 50);
end;
procedureTTests.TestArrayConcatenation(const A1, A2: TArray<Int64>);
var
Combined: TArray<Int64>;
begin
SetLength(Combined, Length(A1) + Length(A2));
// ... concatenation logic ...
Assert.AreEqual(Length(A1) + Length(A2), Length(Combined));
end;

Configuring Iterations

Use the ForAll attribute to specify the number of test iterations (default: 10).

[ForAll(1000)]
procedureTestWithManyIterations([IntRange(1, 100)] constValue: Integer);

Multiple Parameters

Property tests can accept multiple parameters with different strategies.

Using Attribute-Based Generation

[ForAll(100)]
procedureTestAddition([IntRange(-1000, 1000)] const A: Integer;
[IntRange(-1000, 1000)] const B: Integer);
beginconst Sum = Int64(A) + Int64(B);
Assert.AreEqual(Sum, Int64(B) + Int64(A), 'Addition should be commutative');
end;

Using Generator-Based Execution

For more complex scenarios or when using collections, you can pass generators directly:

[Test]
procedureRunTestComplexScenario;
procedureTestComplexScenario(const Values: TArray<Int64>; constName: string);
// ImplementationprocedureTTests.RunTestComplexScenario;
begin
THypothesis.Run(Self, 'TestComplexScenario', [
THypothesis.ArrayOfIntegers(5, 10, 1, 100),
THypothesis.StringAlpha(3, 20) // Note: StringAlpha not yet implemented as helper
], 50);
end;

Note: Currently, only collection helpers are available. String and other type helpers are planned for future versions.

How Shrinking Works

When a test fails, Hypothesis automatically searches for a simpler failing example:

  • Integers: Binary search towards zero within valid range
  • Strings: Reduce length and simplify characters
  • Booleans: True shrinks to False
  • Floats: Special values → zero → nearest integer → halfway towards zero
  • Dates: Year shrinks towards 2000, month towards January, day towards 1st
  • DateTimes: Date components shrink as above, time shrinks towards midnight
  • Times: Hour/minute/second/millisecond each shrink towards zero

Example failure output:

Property test failed on iteration 47/100
Original values: 'AbCdEfGhIjKlMnOp', 42
Minimal failing example: 'A', 1
Seed: 12345678

Best Practices

  1. Keep properties simple: Each test should verify one property
  2. Use appropriate ranges: Don't generate more values than needed
  3. Handle edge cases: Consider empty strings, zero, negative numbers
  4. Use meaningful parameter names: The ParamName helps in error messages
  5. Start with fewer iterations: Use 10-100 iterations during development, increase for CI

Examples

The project includes two types of examples:

Example Code

Delphi.Source/examples/Hypothesis.Examples.pas - Simple utility classes demonstrating testable code:

  • TStringUtils: String manipulation functions
  • TMathUtils: Mathematical operations

Example Tests

Delphi.Source/examples/Hypothesis.Examples.Tests.pas - Property-based tests for the example code:

  • String reversal properties (involutive, length preservation)
  • Arithmetic properties (commutativity, associativity)
  • Consistency checks

Framework Tests

Delphi.Source/tests/Hypothesis.Core.Tests.pas - Comprehensive test suite including:

  • Integer properties (reverse, addition, absolute value)
  • String properties (reverse, concatenation, uppercase)
  • Strategy validation (positive, negative, non-zero, alpha, numeric)
  • Combined integer and string properties

Architecture

The library consists of the following main components:

  1. Hypothesis.Attributes: Custom attributes for declaring strategies (ForAll, IntRange, StringAlpha, etc.)
  2. Hypothesis.Generators.*: Value generators with shrinking logic
    • Hypothesis.Generators.Interfaces: Base interface for all generators
    • Hypothesis.Generators.Integers: Integer value generation and shrinking
    • Hypothesis.Generators.Strings: String value generation and shrinking
    • Hypothesis.Generators.Factory: Creates appropriate generators from attributes
  3. Hypothesis.Core: Test runner with RTTI-based parameter inspection and shrinking orchestration
  4. Hypothesis.Runner: DUnitX framework integration helper (THypothesis.Run)
  5. Hypothesis.Exceptions: Custom exception types for property test failures

Limitations

Current implementation includes:

✅ Supported Types:

  • Integers (Int64)
  • Strings (with multiple character sets)
  • Booleans
  • Floats/Doubles (with special values)
  • Dates, DateTimes, and Times
  • Collections (TArray, IList, IDictionary via helper methods)

⚠️ Collection Limitations:

  • Collections require manual instantiation using helper methods
  • Cannot use attribute-based generation for collections (Delphi language limitation)
  • Collection types are limited to: Int64, string, Double, Boolean

📋 Not Yet Implemented:

  • Records and custom object types
  • Advanced shrinking strategies
  • Database/persistence for test cases
  • Stateful testing
  • Custom strategy composition
  • Full generic collection support

Future versions may add support for records, advanced shrinking, and more flexible collection generators.

Troubleshooting

"Parameter has no strategy attribute"

Ensure all test method parameters have a strategy attribute (e.g., IntRange, StringAlpha).

"Unsupported attribute type"

Check that you're using one of the supported strategy attributes.

Tests are too slow

Reduce the number of iterations in the ForAll attribute or narrow the value ranges.

Contributing

This is an experimental library. Feedback and contributions are welcome.

License

[Specify your license here]

Acknowledgments

Inspired by the excellent Hypothesis library for Python.

About

Delphi implementation for the Python Hyopthesis library. Meant for unit testing strategies.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - GDKsoftware/Hypothesis4D: Delphi implementation for the Python Hyopthesis library. Meant for unit testing strategies. · GitHub
Skip to content

Repository files navigation

Hypothesis for Delphi

A minimal property-based testing library for Delphi, inspired by Python's Hypothesis library.

Overview

Hypothesis for Delphi enables property-based testing using custom attributes and automatic value generation with shrinking support. Instead of writing individual test cases with specific values, you define properties that should hold true for all valid inputs, and Hypothesis generates test data automatically.

Property-Based Testing vs Traditional Testing

Traditional unit tests verify behavior with specific, hand-picked examples: "when I reverse 'hello', I get 'olleh'". Property-based testing takes a different approach by defining general rules: "reversing any string twice returns the original". Hypothesis then automatically generates hundreds of test cases to verify this property holds for empty strings, single characters, long strings, special characters, and edge cases you might not have thought of. When a property fails, Hypothesis automatically "shrinks" the failing input to find the simplest example that breaks your code, making debugging much easier. This approach catches corner cases that manual testing often misses, while requiring less test code to write and maintain. Property-based testing complements traditional example-based tests perfectly: use examples for specific known scenarios and edge cases, and use properties to verify general behavior across a wide range of inputs.

Features

  • Automatic Value Generation: Generate random test data based on declarative strategies
  • Smart Shrinking: When a test fails, automatically finds the minimal failing example
  • DUnitX Integration: Seamless integration with the DUnitX test framework
  • Type-Safe: Leverages Delphi's RTTI and generics for type safety
  • Configurable: Control iteration counts and random seeds for reproducibility

Requirements

  • Delphi 11 (or newer)
  • DUnitX test framework
  • Spring4D collections library

Installation

  1. Add the Delphi.Source\src folder to your project's search path
  2. Ensure DUnitX and Spring4D are available in your project
  3. Add the required units to your test project:
    • Hypothesis.Attributes - Strategy attribute declarations
    • Hypothesis.Runner - Test execution helper
    • Hypothesis.Core - Core test runner (automatically included)
    • Hypothesis.Generators.* - Value generators (automatically included)

Quick Start

unit MyTests;
interfaceuses
DUnitX.TestFramework,
Hypothesis.Attributes,
Hypothesis.Runner;
type
[TestFixture]
TMyPropertyTests = classpublic
[Test]
procedureRunTestStringReverse;
[ForAll(100)]
procedureTestStringReverse([StringAlpha(0, 50)] const Text: string);
end;
implementationuses
System.SysUtils;
functionReverseString(const S: string): string;
begin
Result := '';
forvar I := S.Length downto1do
Result := Result + S[I];
end;
procedureTMyPropertyTests.RunTestStringReverse;
begin
THypothesis.Run(Self, 'TestStringReverse');
end;
procedureTMyPropertyTests.TestStringReverse(const Text: string);
beginvar Reversed := ReverseString(Text);
var DoubleReversed := ReverseString(Reversed);
Assert.AreEqual(Text, DoubleReversed,
'Reversing a string twice should give the original string');
end;
end.

Strategy Attributes

Integer Strategies

IntRange(Min, Max)

Generates integers within the specified range (inclusive).

[Test]
procedureRunTestIntRange;
[ForAll(100)]
procedureTestIntRange([IntRange(-100, 100)] constValue: Integer);
// ImplementationprocedureTMyTests.RunTestIntRange;
begin
THypothesis.Run(Self, 'TestIntRange');
end;

IntPositive(Max)

Generates positive integers from 1 to Max (inclusive).

[ForAll(100)]
procedureTestPositive([IntPositive(1000)] const Count: Integer);

IntNegative(Min)

Generates negative integers from Min to -1 (inclusive).

[ForAll(100)]
procedureTestNegative([IntNegative(-1000)] const Debt: Integer);

IntNonZero(Min, Max)

Generates integers in the range Min to Max, excluding zero.

[ForAll(100)]
procedureTestNonZero([IntNonZero(-100, 100)] const Divisor: Integer);

String Strategies

StringGen(MinLen, MaxLen)

Generates strings with arbitrary printable characters.

[ForAll(100)]
procedureTestAnyString([StringGen(0, 100)] const Text: string);

StringAlpha(MinLen, MaxLen)

Generates strings containing only alphabetic characters (A-Z, a-z).

[ForAll(100)]
procedureTestAlphaString([StringAlpha(1, 50)] constName: string);

StringNumeric(MinLen, MaxLen)

Generates strings containing only numeric digits (0-9).

[ForAll(100)]
procedureTestNumericString([StringNumeric(5, 10)] const Code: string);

Boolean Strategies

Boolean

Generates boolean values (True or False).

[ForAll(100)]
procedureTestBoolean([Boolean] const Flag: Boolean);

Float/Double Strategies

FloatRange(Min, Max, AllowNaN, AllowInfinity)

Generates floating-point values within the specified range.

[ForAll(100)]
procedureTestFloatRange([FloatRange(-100.0, 100.0)] constValue: Double);
// With special values
[ForAll(100)]
procedureTestFloatSpecial([FloatRange(-10.0, 10.0, True, True)] constValue: Double);

FloatPositive(Max)

Generates positive floating-point values greater than zero.

[ForAll(100)]
procedureTestPositiveFloat([FloatPositive(1000.0)] const Amount: Double);

FloatNegative(Min)

Generates negative floating-point values less than zero.

[ForAll(100)]
procedureTestNegativeFloat([FloatNegative(-1000.0)] const Debt: Double);

FloatUnit

Generates floating-point values in the unit interval [0.0, 1.0].

[ForAll(100)]
procedureTestProbability([FloatUnit] const Probability: Double);

Date/DateTime Strategies

DateRange(MinYear, MaxYear)

Generates date values (TDate) within the specified year range.

[ForAll(100)]
procedureTestDate([DateRange(1900, 2100)] const Date: TDate);

DateTimeRange(MinYear, MaxYear)

Generates datetime values (TDateTime) with both date and time components.

[ForAll(100)]
procedureTestDateTime([DateTimeRange(1900, 2100)] const DT: TDateTime);

DateRecent(Days)

Generates recent dates within the specified number of days from today.

[ForAll(100)]
procedureTestRecentDate([DateRecent(30)] const Date: TDate);

TimeRange

Generates time values (TTime) representing time of day (00:00:00 to 23:59:59).

[ForAll(100)]
procedureTestTime([TimeRange] const Time: TTime);

Collection Strategies

Collection generators (arrays, lists, dictionaries) require manual instantiation due to Delphi's compile-time constant requirements in attributes. Hypothesis provides convenient helper methods to simplify collection generation.

Using Collection Helpers

Instead of using attributes, use the helper methods with the generator-based Run overload:

type
[TestFixture]
TCollectionTests = classpublic
[Test]
procedureRunTestArraySum;
procedureTestArraySum(const Values: TArray<Int64>);
end;
implementationprocedureTCollectionTests.RunTestArraySum;
begin
THypothesis.Run(Self, 'TestArraySum', [
THypothesis.ArrayOfIntegers(5, 10, 1, 100)
], 50);
end;
procedureTCollectionTests.TestArraySum(const Values: TArray<Int64>);
var
Sum: Int64;
I: Integer;
begin
Sum := 0;
for I := 0to High(Values) do
Sum := Sum + Values[I];
Assert.IsTrue(Sum > 0, 'Sum of positive integers should be positive');
Assert.IsTrue(Length(Values) >= 5);
Assert.IsTrue(Length(Values) <= 10);
end;

Array Helper Methods

  • ArrayOfIntegers(MinCount, MaxCount, MinValue, MaxValue, ExcludeZero) - Arrays of Int64
  • ArrayOfStrings(MinCount, MaxCount, MinLen, MaxLen, CharSet) - Arrays of strings
  • ArrayOfFloats(MinCount, MaxCount, MinValue, MaxValue, AllowNaN, AllowInfinity) - Arrays of Double
  • ArrayOfBooleans(MinCount, MaxCount) - Arrays of Boolean
  • ArrayOf(MinCount, MaxCount, ElementGenerator, ElementTypeInfo) - Generic array generator
// Generate array of 3-7 strings, each 5-15 characters long
THypothesis.ArrayOfStrings(3, 7, 5, 15, TStringCharSet.Alpha)
// Generate array of 10-20 floats between -1.0 and 1.0
THypothesis.ArrayOfFloats(10, 20, -1.0, 1.0)

List Helper Methods

Lists use Spring4D's IList<T> interface:

  • ListOfIntegers(MinCount, MaxCount, MinValue, MaxValue, ExcludeZero) - Lists of Int64
  • ListOfStrings(MinCount, MaxCount, MinLen, MaxLen, CharSet) - Lists of strings
  • ListOfFloats(MinCount, MaxCount, MinValue, MaxValue, AllowNaN, AllowInfinity) - Lists of Double
  • ListOfBooleans(MinCount, MaxCount) - Lists of Boolean
  • ListOf(MinCount, MaxCount, ElementGenerator, ElementTypeInfo) - Generic list generator
procedureTTests.RunTestListContains;
begin
THypothesis.Run(Self, 'TestListContains', [
THypothesis.ListOfIntegers(5, 10, 1, 100)
], 50);
end;
procedureTTests.TestListContains(const Values: IList<Int64>);
beginif Values.Count > 0then
Assert.IsTrue(Values.Contains(Values[0]));
end;

Dictionary Helper Methods

Dictionaries use Spring4D's IDictionary<K,V> interface:

  • DictIntegerToString(MinCount, MaxCount, KeyMin, KeyMax, ValueMinLen, ValueMaxLen, ValueCharSet) - Int64 → string
  • DictStringToInteger(MinCount, MaxCount, KeyMinLen, KeyMaxLen, KeyCharSet, ValueMin, ValueMax) - string → Int64
  • DictStringToString(MinCount, MaxCount, KeyMinLen, KeyMaxLen, KeyCharSet, ValueMinLen, ValueMaxLen, ValueCharSet) - string → string
  • DictOf(MinCount, MaxCount, KeyGenerator, ValueGenerator, KeyTypeInfo, ValueTypeInfo) - Generic dictionary generator
// Generate dictionary with 3-8 entries, Int64 keys (1-100), string values (5-15 chars)
THypothesis.DictIntegerToString(3, 8, 1, 100, 5, 15, TStringCharSet.Alpha)

Multiple Collection Parameters

You can pass multiple generators to test interactions between collections:

procedureTTests.RunTestArrayConcatenation;
begin
THypothesis.Run(Self, 'TestArrayConcatenation', [
THypothesis.ArrayOfIntegers(3, 5, 1, 100),
THypothesis.ArrayOfIntegers(3, 5, 1, 100)
], 50);
end;
procedureTTests.TestArrayConcatenation(const A1, A2: TArray<Int64>);
var
Combined: TArray<Int64>;
begin
SetLength(Combined, Length(A1) + Length(A2));
// ... concatenation logic ...
Assert.AreEqual(Length(A1) + Length(A2), Length(Combined));
end;

Configuring Iterations

Use the ForAll attribute to specify the number of test iterations (default: 10).

[ForAll(1000)]
procedureTestWithManyIterations([IntRange(1, 100)] constValue: Integer);

Multiple Parameters

Property tests can accept multiple parameters with different strategies.

Using Attribute-Based Generation

[ForAll(100)]
procedureTestAddition([IntRange(-1000, 1000)] const A: Integer;
[IntRange(-1000, 1000)] const B: Integer);
beginconst Sum = Int64(A) + Int64(B);
Assert.AreEqual(Sum, Int64(B) + Int64(A), 'Addition should be commutative');
end;

Using Generator-Based Execution

For more complex scenarios or when using collections, you can pass generators directly:

[Test]
procedureRunTestComplexScenario;
procedureTestComplexScenario(const Values: TArray<Int64>; constName: string);
// ImplementationprocedureTTests.RunTestComplexScenario;
begin
THypothesis.Run(Self, 'TestComplexScenario', [
THypothesis.ArrayOfIntegers(5, 10, 1, 100),
THypothesis.StringAlpha(3, 20) // Note: StringAlpha not yet implemented as helper
], 50);
end;

Note: Currently, only collection helpers are available. String and other type helpers are planned for future versions.

How Shrinking Works

When a test fails, Hypothesis automatically searches for a simpler failing example:

  • Integers: Binary search towards zero within valid range
  • Strings: Reduce length and simplify characters
  • Booleans: True shrinks to False
  • Floats: Special values → zero → nearest integer → halfway towards zero
  • Dates: Year shrinks towards 2000, month towards January, day towards 1st
  • DateTimes: Date components shrink as above, time shrinks towards midnight
  • Times: Hour/minute/second/millisecond each shrink towards zero

Example failure output:

Property test failed on iteration 47/100
Original values: 'AbCdEfGhIjKlMnOp', 42
Minimal failing example: 'A', 1
Seed: 12345678

Best Practices

  1. Keep properties simple: Each test should verify one property
  2. Use appropriate ranges: Don't generate more values than needed
  3. Handle edge cases: Consider empty strings, zero, negative numbers
  4. Use meaningful parameter names: The ParamName helps in error messages
  5. Start with fewer iterations: Use 10-100 iterations during development, increase for CI

Examples

The project includes two types of examples:

Example Code

Delphi.Source/examples/Hypothesis.Examples.pas - Simple utility classes demonstrating testable code:

  • TStringUtils: String manipulation functions
  • TMathUtils: Mathematical operations

Example Tests

Delphi.Source/examples/Hypothesis.Examples.Tests.pas - Property-based tests for the example code:

  • String reversal properties (involutive, length preservation)
  • Arithmetic properties (commutativity, associativity)
  • Consistency checks

Framework Tests

Delphi.Source/tests/Hypothesis.Core.Tests.pas - Comprehensive test suite including:

  • Integer properties (reverse, addition, absolute value)
  • String properties (reverse, concatenation, uppercase)
  • Strategy validation (positive, negative, non-zero, alpha, numeric)
  • Combined integer and string properties

Architecture

The library consists of the following main components:

  1. Hypothesis.Attributes: Custom attributes for declaring strategies (ForAll, IntRange, StringAlpha, etc.)
  2. Hypothesis.Generators.*: Value generators with shrinking logic
    • Hypothesis.Generators.Interfaces: Base interface for all generators
    • Hypothesis.Generators.Integers: Integer value generation and shrinking
    • Hypothesis.Generators.Strings: String value generation and shrinking
    • Hypothesis.Generators.Factory: Creates appropriate generators from attributes
  3. Hypothesis.Core: Test runner with RTTI-based parameter inspection and shrinking orchestration
  4. Hypothesis.Runner: DUnitX framework integration helper (THypothesis.Run)
  5. Hypothesis.Exceptions: Custom exception types for property test failures

Limitations

Current implementation includes:

✅ Supported Types:

  • Integers (Int64)
  • Strings (with multiple character sets)
  • Booleans
  • Floats/Doubles (with special values)
  • Dates, DateTimes, and Times
  • Collections (TArray, IList, IDictionary via helper methods)

⚠️ Collection Limitations:

  • Collections require manual instantiation using helper methods
  • Cannot use attribute-based generation for collections (Delphi language limitation)
  • Collection types are limited to: Int64, string, Double, Boolean

📋 Not Yet Implemented:

  • Records and custom object types
  • Advanced shrinking strategies
  • Database/persistence for test cases
  • Stateful testing
  • Custom strategy composition
  • Full generic collection support

Future versions may add support for records, advanced shrinking, and more flexible collection generators.

Troubleshooting

"Parameter has no strategy attribute"

Ensure all test method parameters have a strategy attribute (e.g., IntRange, StringAlpha).

"Unsupported attribute type"

Check that you're using one of the supported strategy attributes.

Tests are too slow

Reduce the number of iterations in the ForAll attribute or narrow the value ranges.

Contributing

This is an experimental library. Feedback and contributions are welcome.

License

[Specify your license here]

Acknowledgments

Inspired by the excellent Hypothesis library for Python.

About

Delphi implementation for the Python Hyopthesis library. Meant for unit testing strategies.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages