Python属性基测试Hypothesis

发布时间:2026/5/28 17:51:10

Python属性基测试Hypothesis Hypothesis 属性基测试Property-Based Testing传统测试用例是基于示例的——你手动编写输入和期望输出。属性基测试则描述代码应该满足的属性不变式由框架自动生成大量测试数据发现隐藏的边界情况。一、安装与基础--------------# pip install hypothesis二、代码示例------------from hypothesis import given, assume, example, find, settingsfrom hypothesis import strategies as stimport pytest# # 1. given 基本用法自动生成测试数据# given(st.integers())def test_integer_identity(x):测试整数自反性任何整数都应等于自身print(f\n测试整数: {x})assert x xgiven(st.text())def test_text_operations(text):测试文本操作的通用属性print(f\n测试文本: {text} (长度: {len(text)}))# 属性1反转两次等于原字符串assert text[::-1][::-1] text# 属性2字符串长度非负assert len(text) 0# # 2. 常用策略Strategies# given(st.lists(st.integers(min_value-100, max_value100)))def test_list_sorting(lst):测试列表排序的属性print(f\n原始列表: {lst[:5]}{... if len(lst) 5 else })sorted_lst sorted(lst)# 属性1排序后长度不变assert len(sorted_lst) len(lst)# 属性2排序后第一个元素是最小值如果列表非空if lst:assert sorted_lst[0] min(lst)assert sorted_lst[-1] max(lst)# 属性3排序后列表是非递减的for i in range(len(sorted_lst) - 1):assert sorted_lst[i] sorted_lst[i 1]given(st.dictionaries(keysst.text(min_size1, max_size10),valuesst.integers(min_value0, max_value10000),min_size1,max_size10))def test_dictionary_properties(d):测试字典的属性print(f\n字典: {d})# 属性1键值对数量正确items list(d.items())assert len(items) len(d)# 属性2所有键都在字典中for key in d.keys():assert key in d# 属性3pop 后键不再存在if d:key, value d.popitem()assert key not in dgiven(st.floats(allow_nanFalse, allow_infinityFalse))def test_float_properties(x):测试浮点数属性排除 NaN 和 Infinityprint(f\n浮点数: {x})# 属性任何数乘以 1 等于自身assert x * 1.0 x# 属性任何数加 0 等于自身assert x 0.0 x# # 3. example指定具体测试用例# given(st.lists(st.integers()))example([]) # 空列表边界情况example([0]) # 单元素列表example([-1, 0, 1]) # 包含负数、零、正数def test_list_with_examples(lst):example 确保边界情况总是被测试到print(f\n测试列表: {lst})# 测试 max/min 对空列表的处理if not lst:with pytest.raises(ValueError):max(lst)else:assert max(lst) min(lst)# # 4. assume()过滤无效输入# given(st.integers())def test_division_property(x):测试除法属性过滤掉除数为零的情况# 使用 assume 排除 x0而不是让测试崩溃assume(x ! 0)result 100 / xprint(f\n100 / {x} {result})assert isinstance(result, (int, float))given(st.text())def test_valid_email_format(email):测试邮件地址格式过滤无效输入# 假设邮件包含 符号assume( in email)assume(len(email) 5)# 提取用户名和域名username, domain email.split(, 1)print(f\n用户名: {username}, 域名: {domain})assert len(username) 0assert . in domain# # 5. strategies.composite自定义策略# st.compositedef user_data_strategy(draw):自定义策略生成模拟用户数据# draw() 从其他策略中抽取具体值username draw(st.text(min_size3, max_size20, alphabetabcdefghijklmnopqrstuvwxyz_))age draw(st.integers(min_value18, max_value120))email draw(st.emails())# 构造用户数据字典return {username: username,age: age,email: email,is_active: draw(st.booleans()),}given(user_data_strategy())def test_user_data_validation(user):测试自定义策略生成的用户数据print(f\n用户数据: {user})# 验证用户名格式assert len(user[username]) 3assert all(c.isalnum() or c _ for c in user[username])# 验证年龄范围assert 18 user[age] 120# 验证邮箱格式assert in user[email]print(f用户 {user[username]} 数据验证通过)# # 6. find() 查找反例# def buggy_sort(lst):一个有 bug 的排序函数无法正确处理重复元素if len(lst) 1:return lst# 故意引入 bug使用 set 去重unique list(set(lst))return sorted(unique)def test_find_counterexample():使用 find() 找到 buggy_sort 的反例def is_sorted(lst):检查列表是否已排序的辅助函数return all(lst[i] lst[i 1] for i in range(len(lst) - 1))# find() 会搜索使条件成立的输入# 这里寻找使 buggy_sort 输出与输入长度不同的输入def length_changed(lst):sorted_lst buggy_sort(lst)return len(sorted_lst) ! len(lst)try:# 查找反例输入和排序后长度不同counterexample find(st.lists(st.integers(min_value0, max_value10), min_size2),length_changed)print(f\n找到反例输入: {counterexample})print(f排序前长度: {len(counterexample)})print(f排序后长度: {len(buggy_sort(counterexample))})print(说明含有重复元素的列表在排序后丢失了元素)except Exception as e:print(f未找到反例: {e})# # 7. Health Checks 和 Settings# # 缓存的函数测试时需要注意状态残留cache {}def cached_fibonacci(n):带缓存的斐波那契数列计算if n 0:raise ValueError(n 必须为非负整数)if n in cache:return cache[n]if n 1:cache[n] nelse:cache[n] cached_fibonacci(n - 1) cached_fibonacci(n - 2)return cache[n]given(st.integers(min_value0, max_value20))settings(max_examples50, suppress_health_checklist(HealthCheck)) # 抑制健康检查def test_fibonacci_property(n):测试斐波那契数列的属性from hypothesis import HealthCheckprint(f\nFibonacci({n}) {cached_fibonacci(n)})# 属性F(n) n 对于 n 3 成立if n 3:assert cached_fibonacci(n) n# 属性F(n) F(n-1) F(n-2) 对于 n 2 成立if n 2:assert cached_fibonacci(n) cached_fibonacci(n - 1) cached_fibonacci(n - 2)# # 8. 测试边缘情况的自动发现# given(st.lists(st.integers()))settings(max_examples200)def test_auto_edge_cases(lst):Hypothesis 自动发现边界情况# 对列表反转两次reversed_twice lst[::-1][::-1]assert reversed_twice lstprint(f\n列表长度: {len(lst)}, 反转两次验证通过)# # 9. 基于属性的测试 vs 传统参数化测试# # 传统参数化测试手动列举pytest.mark.parametrize(a, b, [(1, 2),(0, 0),(-1, 1),(100, -50),])def test_traditional_addition(a, b):传统参数化测试只能测试手动列举的用例assert a b - b a# Hypothesis 属性测试自动生成given(st.integers(), st.integers())def test_hypothesis_addition(a, b):Hypothesis 测试描述属性自动生成大量数据# 加法的一个属性a b - b 应该永远等于 aassert a b - b a# 另一个属性加法交换律assert a b b a# # 10. 测试自定义数据结构的属性# class Stack:一个简单的栈数据结构def __init__(self):self._items []def push(self, item):self._items.append(item)def pop(self):if not self._items:raise IndexError(从空栈中弹出)return self._items.pop()def peek(self):if not self._items:raise IndexError(从空栈中查看)return self._items[-1]def is_empty(self):return len(self._items) 0def size(self):return len(self._items)given(st.lists(st.integers()))def test_stack_properties(items):测试栈数据结构的属性stack Stack()# 属性1新栈是空的assert stack.is_empty()assert stack.size() 0# 将所有元素入栈for item in items:stack.push(item)# 属性2入栈后 size 正确assert stack.size() len(items)# 属性3后进先出LIFOfor expected in reversed(items):assert stack.pop() expected# 属性4全部出栈后栈再次为空assert stack.is_empty()assert stack.size() 0

相关新闻