hegel
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHegel: Property-Based Testing
Hegel:基于属性的测试
Hegel is a universal family of property-based testing, supporting a variety of
languages all powered by Hypothesis. Tests integrate with standard test runners
(e.g. , , , etc.). Hegel generates random inputs
for your code and automatically shrinks failing cases to minimal counterexamples.
cargo testpytestjestHegel是一个通用的基于属性的测试框架家族,依托Hypothesis支持多种编程语言。测试可与标准测试运行器(如、、等)集成。Hegel会为你的代码生成随机输入,并自动将失败案例简化为最小化的反例。
cargo testpytestjestWorkflow
工作流程
Follow these steps when writing property-based tests.
编写基于属性的测试时,请遵循以下步骤:
1. Detect Language and Load References
1. 检测语言并加载参考文档
Identify the project language from build files:
| File | Language | Reference |
|---|---|---|
| Rust | |
Load the corresponding reference file for API details and idiomatic patterns.
通过构建文件识别项目语言:
| 文件 | 语言 | 参考文档 |
|---|---|---|
| Rust | |
加载对应的参考文档,获取API细节和惯用模式。
2. Explore the Code Under Test
2. 研究待测试代码
Before writing any test, understand what you're testing:
- Read the source code of the function/module under test
- Read existing tests to understand expected behavior and edge cases
- Read docstrings, comments, and type signatures for documented contracts
- Read usage sites to see how callers use the code and what they expect
The goal is to find evidence for properties, not to invent them.
在编写任何测试前,先理解待测试内容:
- 阅读待测试函数/模块的源代码
- 阅读现有测试,了解预期行为和边缘情况
- 阅读文档字符串、注释和类型签名,获取已记录的契约
- 阅读使用场景,了解调用者如何使用代码及他们的预期
目标是为属性找到依据,而非凭空创造。
3. Identify Valuable Properties
3. 确定有价值的属性
Look for properties that are:
- Grounded in evidence from the code, docs, or usage patterns
- Non-trivial — they test real behavior, not tautologies, and do not duplicate the code being tested
- Falsifiable — a buggy implementation could actually violate them
Write one test per property. Don't cram multiple properties into one test.
寻找具备以下特征的属性:
- 有依据支撑:来自代码、文档或使用模式
- 非平凡:测试真实行为,而非同义反复,且不重复待测试代码的逻辑
- 可证伪:存在问题的实现确实会违反该属性
每个属性对应一个测试,不要将多个属性塞进同一个测试中。
4. Check for Existing Tests to Evolve or Port
4. 检查是否有可改进或迁移的现有测试
Before writing tests from scratch, always check existing tests:
- Existing PBTs in another framework (proptest, quickcheck, etc.) should be
ported to hegel. Load for general guidance and the language-specific porting reference (e.g.,
references/porting.md). Don't carry over narrow generator bounds from the old framework — use broader generators unless bounds are justified by the function's contract.references/porting-rust.md - Unit tests and example-based tests can often be evolved into PBTs. Load
for guidance. Tests with hardcoded seeds, parameterized examples, or multiple similar test cases are prime candidates.
references/evolving-tests.md - Tests that use with fixed seeds are especially good candidates — the randomness should come from hegel instead so failures produce shrinkable counterexamples.
rand
When you evolve an existing test, modify the existing test file rather than
creating a new one. Add hegel tests alongside (or replacing) the existing tests
in the same file where the original tests live. Do not create a separate
or similar — property-based tests are tests like any other and
belong with the code they're testing.
test_hegel.rs在从头编写测试前,务必检查现有测试:
- 其他框架中的现有PBT(如proptest、quickcheck等)应迁移到Hegel。加载获取通用指导,以及对应语言的迁移参考文档(如
references/porting.md)。不要保留旧框架中过窄的生成器边界——除非函数契约有明确要求,否则使用更宽泛的生成器。references/porting-rust.md - 单元测试和基于示例的测试通常可以改进为PBT。加载获取指导。包含硬编码种子、参数化示例或多个相似测试用例的测试是优先改进的候选。
references/evolving-tests.md - 使用带固定种子的的测试是极佳的改进候选——随机性应来自Hegel,这样失败时能生成可简化的反例。
rand
改进现有测试时,修改现有的测试文件而非创建新文件。在原有测试所在的同一文件中添加Hegel测试(或替换原有测试)。不要创建单独的或类似文件——基于属性的测试和其他测试一样,应与待测试代码放在一起。
test_hegel.rs5. Write the Tests
5. 编写测试
For each property:
- Add tests to the appropriate existing test file. If there's already a
covering the module, add hegel tests there. Only create a new file if no relevant test file exists.
test_foo.rs - Choose the simplest possible generators — start with no bounds, unless
bounds are logically necessary (e.g. if a number has to be non-zero it's
fine to force it to be, but lists should not have set unless there is a compelling correctness reason to set them or poor performance has been observed when actually running the test)
max_size - Draw values using
tc.draw() - Run the code under test
- Assert the property
针对每个属性:
- 将测试添加到合适的现有测试文件中。如果已有覆盖该模块,就在此添加Hegel测试。仅当没有相关测试文件时才创建新文件。
test_foo.rs - 选择最简单的生成器——从无边界开始,除非逻辑上必须设置边界(例如数字必须非零,这没问题,但列表不应设置,除非有令人信服的正确性理由或实际运行测试时发现性能问题)。
max_size - 使用获取值
tc.draw() - 运行待测试代码
- 断言属性
6. Run and Reflect
6. 运行并反思
Run the tests. When a test fails, ask:
- Is this a real bug? If the code violates its own contract, flag the bug to the user and ask what to do, or fix the code if instructed to do so.
- Is the property unsound? If you asserted something the code never promised, fix the test.
- Is the generator too broad? Only if the failing input is genuinely outside the function's domain, add constraints. Investigate before constraining.
运行测试。当测试失败时,思考:
- **这是真实的bug吗?**如果代码违反了自身契约,向用户标记该bug并询问处理方式,或按指示修复代码。
- **属性是否不合理?**如果你断言了代码从未承诺的内容,修复测试。
- **生成器是否过于宽泛?**仅当失败输入确实超出函数的定义域时,再添加约束。在约束前先进行调查。
Property Categories
属性分类
Use this taxonomy to identify what to test. Not every category applies to every
function — pick the ones supported by evidence.
| Category | Description | Example |
|---|---|---|
| Round-trip | encode then decode recovers the original | |
| Idempotence | applying twice equals applying once | |
| Commutativity | order of operations doesn't matter | |
| Invariant preservation | an operation maintains a structural property | |
| Oracle / reference impl | compare against a known-correct implementation | |
| Monotonicity | more input means more (or equal) output | |
| Bounds / contracts | output stays within documented limits | |
| No-crash / robustness | function handles all valid inputs without panicking | |
| Equivalence | two implementations produce the same result | |
| Model-based | operations on real system match a simplified model | |
| Consistency | related APIs in the same library agree | |
| Precision preservation | numeric values survive format conversions | |
使用此分类法确定测试内容。并非每个分类都适用于所有函数——选择有依据支撑的分类。
| 分类 | 描述 | 示例 |
|---|---|---|
| 往返测试 | 编码后再解码可恢复原始值 | |
| 幂等性测试 | 执行两次等同于执行一次 | |
| 交换律测试 | 操作顺序不影响结果 | |
| 不变量保留测试 | 操作维持结构属性 | |
| 参考实现对比测试 | 与已知正确的实现对比 | |
| 单调性测试 | 输入越多,输出越多(或相等) | |
| 边界/契约测试 | 输出保持在文档规定的范围内 | |
| 无崩溃/鲁棒性测试 | 函数处理所有有效输入时不会崩溃 | |
| 等价性测试 | 两个实现产生相同结果 | |
| 基于模型的测试 | 真实系统上的操作与简化模型匹配 | |
| 一致性测试 | 同一库中的相关API保持一致 | |
| 精度保留测试 | 数值在格式转换后保持不变 | |
High-Value Patterns (Field-Tested)
高价值测试模式(经实战验证)
These patterns are ranked by how often they found real bugs when tested across
many popular Rust crate libraries. See
for detailed examples.
references/field-tested-patterns.md这些模式在测试众多流行Rust crate库时,多次发现真实bug。详见中的详细示例。
references/field-tested-patterns.md1. Model Tests (Highest Value for Data Structures)
1. 模型测试(对数据结构价值最高)
For any data structure, the highest-value first test is a model test — run
the same operations on the library under test and a known-good reference (usually
a std type), then assert they agree after every operation.
Choose the right oracle:
- for sequential containers (fixed-capacity vecs, small vecs)
Vec - for hash maps (alternative/concurrent hash maps)
HashMap - for ordered maps (tree maps, persistent maps)
BTreeMap - for ordered sets / bitmaps (compressed bitmaps, tree sets)
BTreeSet - for unordered sets (indexed sets, bit sets)
HashSet
对于任何数据结构,价值最高的首个测试是模型测试——在待测试库和已知正确的参考实现(通常是标准库类型)上执行相同操作,然后在每次操作后断言两者结果一致。
选择合适的参考实现:
- 序列容器(固定容量vec、small vec)选
Vec - 哈希映射(替代/并发哈希映射)选
HashMap - 有序映射(树映射、持久化映射)选
BTreeMap - 有序集合/位图(压缩位图、树集合)选
BTreeSet - 无序集合(索引集合、位集合)选
HashSet
2. Idempotence Tests (Highest Value for String/Text Processing)
2. 幂等性测试(对字符串/文本处理价值最高)
Any normalization, case conversion, or formatting function should be idempotent:
. Use (not ASCII-only generators) because
Unicode edge cases like → and combining characters are where bugs hide.
f(f(x)) == f(x)generators::text()ßSS任何归一化、大小写转换或格式化函数都应具备幂等性:。使用(而非仅ASCII的生成器),因为Unicode边缘情况(如→和组合字符)是bug的高发区。
f(f(x)) == f(x)generators::text()ßSS3. Parse Robustness (Universal — Test Every Parser)
3. 解析鲁棒性测试(通用——测试所有解析器)
Every , , or function should be tested with
. The property is simple: it should never panic. Parsers
that delegate to constructors which panic on invalid values (instead of returning
errors) are a common source of bugs.
from_strparsedecodegenerators::text()每个、或函数都应使用测试。属性很简单:它永远不应该panic。委托给会在无效值时panic(而非返回错误)的构造函数的解析器是常见的bug来源。
from_strparsedecodegenerators::text()4. Roundtrip Tests (High Value for Serialization)
4. 往返测试(对序列化价值最高)
parse(format(x)) == x对于任何序列化/反序列化对,成立。测试完整的输入域——不要限制为“合理”值。bug往往隐藏在边界处,比如零(如科学计数法缺少系数)、大整数(值大于2^53时,通过f64中间层会丢失精度)和特殊字符串内容(路径中的双斜杠、控制字符)。
parse(format(x)) == x5. Boundary Value Tests (High Value for Numeric Code)
5. 边界值测试(对数值代码价值最高)
Integer operations should be tested with , , , and unconstrained
ranges. Negating overflows, dividing by overflows, and
many libraries forget to handle these. Don't add
— those bounds hide real bugs.
MINMAX0i32::MINi64::MIN.min_value(-100).max_value(100)整数操作应使用、、和无约束范围进行测试。取反会溢出,除以会溢出,很多库都忘记处理这些情况。不要添加——这些边界会隐藏真实的bug。
MINMAX0i32::MINi64::MIN.min_value(-100).max_value(100)Choosing Properties
生成器规范
Properties must be evidence-based. Find evidence in:
- Names and Type signatures: A function implies the output length might equal the sum of input lengths.
fn merge(a: Vec<T>, b: Vec<T>) -> Vec<T> - Docstrings and comments: "Returns a sorted list" directly gives you an invariant.
- Assertions and debug_asserts in the source: These are properties the author already identified, and do not need to be duplicated in the tests, but may suggest other invariants.
- Usage patterns: If callers always assume a result is non-empty, assert that the result is always non-empty.
- Existing tests: Unit tests often encode specific instances of general properties.
Err on the side of creating more properties rather than fewer, and if they fail investigate whether the failure is legitimate behaviour or not.
Beware of properties that seem universal but aren't. Read the docs carefully
before asserting a property. Examples from real testing:
- Grapheme-based string reverse is NOT an involution (because
reverse(reverse("\n\r")) ≠ "\n\r"is one grapheme cluster while\r\nis two).\n\r - A method called might mean symmetric difference (A △ B), not set difference (A \ B) — check the docs.
difference - A function documented as "returns the largest key ≤ k" means ≤, not <.
When a property fails, investigate whether it's a real bug or a genuine edge case
in the domain. A weaker property often still holds.
代理编写基于属性的测试时,一个常见错误是过度约束生成器。这会导致测试的强度低于预期。
Generator Discipline
从无边界开始
A common mistake agents make when writing property-based tests is over-constraining generators.
This leads to tests that are weaker than they need to be.
如果函数接受任何,使用:
i32rust
generators::integers::<i32>() // 无min_value,无max_value不要预先编写:
rust
generators::integers::<i32>().min_value(0).max_value(100) // 错误,除非有正当理由Start With No Bounds
边缘情况是核心
If the function accepts any , use:
i32rust
generators::integers::<i32>() // no min_value, no max_valueDo NOT preemptively write:
rust
generators::integers::<i32>().min_value(0).max_value(100) // WRONG unless justified不要缩小范围以“避免边缘情况”。边缘情况正是PBT的意义所在。如果函数声称支持所有值,就测试所有值——包括、、、和。
i32i32i32::MINi32::MAX0-11Edge Cases Are the Point
不要默认添加.min_size(1)
.min_size(1)Don't narrow ranges to "avoid edge cases." Edge cases are exactly what PBT is for. If a function claims to work on all values, test it on all values — including , , , , and .
i32i32i32::MINi32::MAX0-11除非函数契约明确要求输入非空,否则也要测试空集合。如果函数在空vec时panic,这可能是一个值得关注的bug。
Don't Add .min_size(1)
by Default
.min_size(1)当测试在极端值上失败时
Unless the function's contract explicitly requires non-empty input, test with empty collections too. If a function panics on an empty vec, that might be a bug worth knowing about.
你的第一反应应该是:这是真实的bug吗?
除非有确凿证据表明不是,否则应假设是bug。如有疑问,询问用户。
- 如果函数文档说明它能处理所有整数,但在时溢出,这是代码的bug,而非测试的问题。
i32::MAX - 仅在调查确认输入超出函数的文档定义域后,再添加边界。
When a Test Fails on Extreme Values
何时添加约束
Your first reaction should be: is this a real bug?
You should assume that it is unless you have strong evidence that it is not. If in doubt, ask the user.
- If the function's documentation says it handles all integers but it overflows on , that's a bug in the code, not in your test.
i32::MAX - Only add bounds after investigating and confirming the input is outside the function's documented domain.
仅在以下情况添加生成器约束:
- 函数契约明确排除某些输入。例如文档要求
fn sqrt(x: f64)。x >= 0 - 需要避免未定义行为。例如除以零。
- 测试失败已调查确认输入超出函数定义域。
When to Add Constraints
尽可能避免拒绝采样
Add generator bounds only when:
- The function's contract explicitly excludes some inputs. For example, documents that
fn sqrt(x: f64)is required.x >= 0 - You need to avoid undefined behavior. For example, division by zero.
- A test failure has been investigated and confirmed to be outside the function's domain.
当约束涉及多个生成值之间的关系时,可以使用:
tc.assume()rust
let a = tc.draw(generators::integers::<i32>());
let b = tc.draw(generators::integers::<i32>());
tc.assume(a != b); // 约束涉及两个值这个例子没问题,但如果可以,最好完全避免:
assume例如:
rust
let a = tc.draw(generators::integers::<i32>());
let b = tc.draw(generators::integers::<i32>().min_value(a));比以下代码更好:
rust
let a = tc.draw(generators::integers::<i32>());
let b = tc.draw(generators::integers::<i32>());
tc.assume(a <= b)更好的写法是:
rust
let mut a = tc.draw(generators::integers::<i32>());
let mut b = tc.draw(generators::integers::<i32>());
if (a > b) {
(a, b) = (b, a);
}在拒绝率可能很高的情况下,尤其要避免拒绝采样。
例如比好得多,因为前者直接构造偶数,而后者会丢弃约50%的测试用例。
st.integers().map(|n| n * 2)st.integers().filter(|n| n % 2 == 0)Avoid rejection sampling where possible
生成大集合
When a constraint involves relationships between multiple generated values, you may use :
tc.assume()rust
let a = tc.draw(generators::integers::<i32>());
let b = tc.draw(generators::integers::<i32>());
tc.assume(a != b); // constraint relates two valuesThis example is perfectly fine, but it is better to avoid altogether when you can:
assumee.g.
rust
let a = tc.draw(generators::integers::<i32>());
let b = tc.draw(generators::integers::<i32>().min_value(a));is better than
rust
let a = tc.draw(generators::integers::<i32>());
let b = tc.draw(generators::integers::<i32>());
tc.assume(a <= b)Even better is:
rust
let mut a = tc.draw(generators::integers::<i32>());
let mut b = tc.draw(generators::integers::<i32>());
if (a > b) {
(a, b) = (b, a);
}It is particularly important to avoid rejection sampling in cases where the rejection rate is likely to be high.
For example is much better than , as the former constructs an even number directly, while the latter throws away around 50% of test cases.
st.integers().map(|n| n * 2)st.integers().filter(|n| n % 2 == 0)Hegel的默认集合大小很小。如果需要大集合(例如,测试深层树路径),单独生成大小并将其作为传入:
min_sizerust
// 推荐——可生成大集合,且便于简化
let n = tc.draw(generators::integers::<usize>().max_value(300));
let keys: Vec<i32> = tc.draw(generators::vecs(generators::integers())
.min_size(n)); // 不设max_size——让Hegel按需生成更大的集合
// 不推荐——Hegel的默认大小分布很少生成100+元素的集合
let keys: Vec<i32> = tc.draw(generators::vecs(generators::integers()));设置但**不设置**是简化优化:Hegel可以缩小以找到触发bug的最小集合大小,同时仍能根据需要添加更多元素。
min_sizemax_sizenGetting Large Collections
使用.unique()
生成键
.unique()Hegel's default collection size is small. If you need large collections (e.g.,
to exercise deep tree paths), draw the size separately and pass it as :
min_sizerust
// GOOD — can generate large collections, shrinks well
let n = tc.draw(generators::integers::<usize>().max_value(300));
let keys: Vec<i32> = tc.draw(generators::vecs(generators::integers())
.min_size(n)); // no max_size — let hegel go bigger if it wants
// BAD — hegel's default size distribution rarely produces 100+ elements
let keys: Vec<i32> = tc.draw(generators::vecs(generators::integers()));Setting but not is a shrinking optimization: hegel can
shrink to find the minimal collection size that triggers the bug, while
still being able to add extra elements if needed.
min_sizemax_sizen测试需要唯一键的映射/集合时:
rust
let keys: Vec<i32> = tc.draw(generators::vecs(generators::integers::<i32>())
.max_size(30).unique());这避免了重复键时的结果歧义。
Use .unique()
for Key Generation
.unique()处理待测试代码中的随机性
When testing maps/sets that need unique keys:
rust
let keys: Vec<i32> = tc.draw(generators::vecs(generators::integers::<i32>())
.max_size(30).unique());This avoids confusion about which value wins for duplicate keys.
当待测试代码需要RNG(例如)时,不要使用Hegel生成的种子创建带种子的RNG,如。这会破坏简化功能——Hegel只能简化种子整数,而不能简化RNG做出的实际随机决策。
fn sample(&self, rng: &mut impl Rng)ChaCha8Rng::seed_from_u64(seed)相反,使用Hegel的特性获取由Hegel控制的RNG。详见对应语言的参考文档获取API细节。
randHandling Randomness in Code Under Test
Rand版本不匹配
When the code under test requires an RNG (e.g., ),
do not create a seeded RNG like with a
hegel-generated seed. This defeats shrinking — hegel can only shrink the seed
integer, not the actual random decisions the RNG makes.
fn sample(&self, rng: &mut impl Rng)ChaCha8Rng::seed_from_u64(seed)Instead, use hegel's feature to get a hegel-controlled RNG. See the
language-specific reference for API details.
randHegel的特性使用rand 0.9。如果项目使用旧版本的rand(如0.8),RNG trait会不兼容。在这种情况下,询问用户是否愿意将项目的rand依赖升级到0.9。这通常很简单(主要API变化是→、→、→、→)。不要默默地退回到带种子的ChaCha——这违背了测试的目的。
randgen_rangerandom_rangegen::<T>()random::<T>()thread_rng()rng()from_entropyfrom_os_rngRand version mismatch
两种模式:人工随机性 vs 真实随机性
Hegel's feature uses rand 0.9. If the project uses an older version of
rand (e.g., 0.8), the RNG traits will be incompatible. In this case, ask the
user whether they'd like to upgrade the project's rand dependency to 0.9. This
is usually straightforward (the main API changes are -> ,
-> , -> ,
-> ). Do not silently fall back to seeded ChaCha —
that defeats the purpose.
randgen_rangerandom_rangegen::<T>()random::<T>()thread_rng()rng()from_entropyfrom_os_rnggenerators::randoms()-
默认(人工随机性):每个随机决策都经过Hegel,支持对单个随机值进行细粒度简化。这是大多数代码的最佳选择。
-
:通过Hegel生成单个种子,然后从中创建真实的
generators::randoms().use_true_random(true)。Hegel只能简化种子,而不能简化单个随机决策。当待测试代码进行拒绝采样或依赖RNG生成统计上看起来随机的输出时使用此模式。人工随机性可能导致拒绝循环挂起,因为受控字节序列看起来不够随机。StdRng
如何选择:从默认模式开始。如果测试因代码内部进行拒绝采样而挂起或超时,切换到。
.use_true_random(true)Two modes: artificial vs true randomness
重构具体的RNG类型
generators::randoms()-
Default (artificial randomness): Every random decision goes through hegel, enabling fine-grained shrinking of individual random values. This is the best option for most code.
-
: Generates a single seed via hegel, then creates a real
generators::randoms().use_true_random(true)from it. Hegel can only shrink the seed, not individual random decisions. Use this when the code under test does rejection sampling or otherwise depends on the RNG producing statistically random-looking output. Artificial randomness can cause rejection loops to hang because the controlled byte sequences don't look random enough.StdRng
How to choose: Start with the default. If tests hang or time out because the
code does rejection sampling internally, switch to .
.use_true_random(true)如果待测试代码接受具体的RNG类型(例如)而非trait约束,考虑是否应重构为接受或。这既是更好的API设计,也能让代码与Hegel的随机生成器兼容。向用户建议此重构。
rng: &mut ChaCha8Rngimpl Rng&mut dyn RngCoreRefactoring concrete RNG types
常见错误
If the code under test takes a concrete RNG type (e.g., )
rather than a trait bound, consider whether it should be refactored to accept
or instead. This is both better API design and
makes the code testable with hegel's random generator. Suggest this refactoring
to the user.
rng: &mut ChaCha8Rngimpl Rng&mut dyn RngCore- 过度约束生成器——“以防万一”添加边界。这会隐藏bug并降低测试价值。详见上方的生成器规范。
- 测试平凡属性——或
assert!(x == x)什么都测试不了。每个属性都应能被有问题的实现证伪。assert!(vec.len() >= 0) - 用实现作为参考——如果测试调用同一个函数计算预期结果,它永远不会失败。使用独立的参考实现(不要复制代码来编写!)、更简单的算法或结构属性。
- 生成范围过宽然后过滤几乎所有值——如果或
.filter()拒绝大多数输入,Hegel会放弃。重构生成器(例如使用tc.assume()或依赖生成)。.map() - 为Hegel测试创建单独的文件——基于属性的测试应与对应代码的现有测试放在一起。不要放在或
test_hegel.rs中——将其添加到现有的测试文件中。test_properties.rs - 使用手动带种子的RNG——不要用Hegel生成种子然后创建。使用带
ChaCha8Rng::seed_from_u64(seed)特性的rand,让Hegel控制随机决策并支持简化。详见上方的“处理随机性”部分。generators::randoms() - 测试代码中出现溢出——从生成的数据计算值时(例如),测试代码本身可能在库出现bug前就溢出。使用包装算术(
map.insert(k, k * 10))或更小的中间类型(生成k.wrapping_mul(10),转换为i16进行乘法)防止这种情况。区分“此约束保护库的契约”(保留)和“此约束防止测试溢出”(改用包装算术)。i32 - 为性能添加——如果测试在大集合时速度慢,降低
.max_size()而非限制输入空间。能发现bug的慢测试胜过无法发现bug的快测试。很多树/前缀树bug仅在50-200+元素时才会显现。test_cases
Common Mistakes
快速设置
—
Rust
- Over-constraining generators — Adding bounds "just in case." This hides bugs and makes tests less valuable. See Generator Discipline above.
- Testing trivial properties — or
assert!(x == x)test nothing. Every property should be falsifiable by a buggy implementation.assert!(vec.len() >= 0) - Using the implementation as the oracle — If your test calls the same function to compute the expected result, it can never fail. Use an independent reference implementation (do not just copy the code to write this!), a simpler algorithm, or a structural property.
- Generating too broadly then filtering almost everything — If or
.filter()rejects most inputs, Hegel will give up. Restructure your generators instead (e.g., usetc.assume()or dependent generation)..map() - Creating a separate test file for hegel tests — Property-based tests belong alongside the existing tests for the same code. Don't put them in or
test_hegel.rs— add them to the existing test files.test_properties.rs - Using manually seeded RNGs — Don't generate a seed with hegel then create . Use
ChaCha8Rng::seed_from_u64(seed)with thegenerators::randoms()feature so hegel controls the random decisions and can shrink them. See "Handling Randomness" above.rand - Overflowing in test code — When computing values from generated data (e.g., ), your test code itself can overflow before the library has a chance to be buggy. Use wrapping arithmetic (
map.insert(k, k * 10)) or smaller intermediate types (drawk.wrapping_mul(10), cast toi16for multiplication) to prevent this. Distinguish "this constraint protects the library's contract" (keep it) from "this constraint prevents my test from overflowing" (use wrapping arithmetic instead).i32 - Adding for performance — If a test is slow with large collections, lower
.max_size()rather than restricting the input space. A slow test that finds bugs beats a fast test that can't. Many tree/trie bugs only manifest at 50-200+ elements.test_cases
toml
undefinedQuick Setup
Cargo.toml
Rust
—
toml
undefined[dev-dependencies]
hegel = { git = "https://github.com/hegeldev/hegel-rust" }
如果待测试代码使用`rand`:
```toml
[dev-dependencies]
hegel = { git = "https://github.com/hegeldev/hegel-rust", features = ["rand"] }要求PATH中包含。运行执行测试。
uvcargo testCargo.toml
—
[dev-dependencies]
hegel = { git = "https://github.com/hegeldev/hegel-rust" }
If the code under test uses `rand`:
```toml
[dev-dependencies]
hegel = { git = "https://github.com/hegeldev/hegel-rust", features = ["rand"] }Requires on PATH. Run with .
uvcargo test—