如何为自定义dataclasses定义Hypothesis策略?能否自动生成?
用Hypothesis自动生成Dataclass测试策略
当然有办法不用手动写每个dataclass的策略,Hypothesis自带的工具就能实现类似Rust arbitrary库的自动类型推导能力,甚至能适配自定义的限定类型(比如你用到的uint64、uint256),下面是具体实现方式:
1. 基础自动生成:直接用from_type
如果你的dataclass字段都是Hypothesis能自动识别的类型(比如int、bytes这类基础类型),直接调用st.from_type()就能生成对应策略:
from dataclasses import dataclass import hypothesis.strategies as st @dataclass class Account: nonce: int balance: int code: bytes # 一行生成Account的测试策略 account_strategy = st.from_type(Account)
2. 适配自定义限定类型:注册类型策略
针对你需要的uint64、uint256这类有范围约束的整数,你可以先定义好对应的策略,再注册给自定义类型,这样Hypothesis就能自动识别并使用对应策略:
from typing import NewType import hypothesis.strategies as st # 定义限定范围的整数策略 uint64 = st.integers(min_value=0, max_value=2**64 - 1) uint256 = st.integers(min_value=0, max_value=2**256 - 1) # 用NewType创建自定义类型别名 Uint64 = NewType('Uint64', int) Uint256 = NewType('Uint256', int) # 注册类型与策略的映射 st.register_type_strategy(Uint64, uint64) st.register_type_strategy(Uint256, uint256) # 定义dataclass时使用自定义类型 @dataclass class Account: nonce: Uint64 balance: Uint256 code: bytes # 自动生成符合要求的Account策略 account_strategy = st.from_type(Account)
3. 更灵活的控制:用Hypothesis的dataclass装饰器
如果需要给某些字段加特殊约束(比如code的字节长度限制),可以用hypothesis.extra.dataclasses里的dataclass装饰器代替标准库的,它会自动集成策略生成能力,还能直接给字段指定策略:
from hypothesis.extra.dataclasses import dataclass from typing import NewType import hypothesis.strategies as st # 先注册自定义类型策略(同上) Uint64 = NewType('Uint64', int) Uint256 = NewType('Uint256', int) st.register_type_strategy(Uint64, st.integers(min_value=0, max_value=2**64 -1)) st.register_type_strategy(Uint256, st.integers(min_value=0, max_value=2**256 -1)) @dataclass class Account: nonce: Uint64 balance: Uint256 # 直接给code字段指定带长度限制的策略 code: bytes = st.binary(min_size=0, max_size=1024).example() # 自动生成策略 account_strategy = st.from_type(Account)
关于从原始字节缓冲区生成
如果一定要像Rust arbitrary那样从原始字节生成结构化数据,你可以先生成随机字节,再自己写解析逻辑映射到dataclass,但这种方式需要手动处理字节解析,容易出错,除非有特殊需求否则不推荐:
def bytes_to_account(raw_bytes: bytes) -> Account: # 按固定格式解析字节:前8字节是uint64的nonce,接下来32字节是uint256的balance,剩余是code nonce = Uint64(int.from_bytes(raw_bytes[:8], byteorder='big')) balance = Uint256(int.from_bytes(raw_bytes[8:40], byteorder='big')) code = raw_bytes[40:] return Account(nonce=nonce, balance=balance, code=code) # 生成原始字节并映射为Account实例 account_from_bytes = st.binary(min_size=40).map(bytes_to_account)
内容的提问来源于stack exchange,提问作者ClementWalter
相关产品推荐
相关产品推荐

