mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-10-03 23:54:12 +00:00
feat: add example plugins (ai_image, calendar, music, rss, weather), fix .gitignore, move gengskill to tools/
This commit is contained in:
346
tools/gengskill/tests/test_modules.py
Normal file
346
tools/gengskill/tests/test_modules.py
Normal file
@ -0,0 +1,346 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Geng Skill 单元测试
|
||||
====================
|
||||
验证各检测模块的正确性、边界条件处理和风险评分一致性。
|
||||
|
||||
运行方式:
|
||||
cd geng-skill
|
||||
python3 -m pytest tests/test_modules.py -v
|
||||
|
||||
或直接运行:
|
||||
python3 tests/test_modules.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import random
|
||||
import math
|
||||
|
||||
# 添加 scripts 目录到路径
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'scripts'))
|
||||
|
||||
from last_digit_test import last_digit_test, extract_last_digit
|
||||
from benford_test import benford_test, get_first_digit
|
||||
from grim_test import grim_test_single, grim_test_batch
|
||||
from fixed_relation_test import fixed_relation_test
|
||||
from decimal_consistency_test import decimal_consistency_test
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Test: Last Digit Test
|
||||
# ============================================================
|
||||
|
||||
class TestLastDigitTest:
|
||||
"""末位数字检测模块测试"""
|
||||
|
||||
def test_uniform_data_low_risk(self):
|
||||
"""均匀分布的末位数字应返回低风险"""
|
||||
random.seed(42)
|
||||
# 生成末位数字均匀的数据
|
||||
values = [f"{random.uniform(1, 100):.2f}" for _ in range(100)]
|
||||
result = last_digit_test(values)
|
||||
assert result['status'] == 'completed'
|
||||
assert result['risk_level'] == 'low'
|
||||
assert result['risk_score'] < 40
|
||||
|
||||
def test_concentrated_digits_high_risk(self):
|
||||
"""集中在某个数字的末位应返回高风险"""
|
||||
# 80% 的末位是 "5"
|
||||
values = ['1.25', '2.35', '3.45', '4.55', '5.65',
|
||||
'6.75', '7.85', '8.95', '9.15', '10.25',
|
||||
'11.35', '12.45', '13.55', '14.65', '15.75',
|
||||
'16.85', '17.95', '18.05', '19.15', '20.55']
|
||||
result = last_digit_test(values)
|
||||
assert result['status'] == 'completed'
|
||||
assert result['most_frequent_digit'] == 5
|
||||
|
||||
def test_insufficient_data(self):
|
||||
"""数据量不足应返回 insufficient_data"""
|
||||
values = ['1.23', '4.56', '7.89']
|
||||
result = last_digit_test(values)
|
||||
assert result['status'] == 'insufficient_data'
|
||||
|
||||
def test_extract_last_digit(self):
|
||||
"""末位数字提取正确性"""
|
||||
assert extract_last_digit('3.14') == 4
|
||||
assert extract_last_digit('100') == 1
|
||||
assert extract_last_digit('-5.67') == 7
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Test: Benford's Law Test
|
||||
# ============================================================
|
||||
|
||||
class TestBenfordTest:
|
||||
"""本福特定律检测模块测试"""
|
||||
|
||||
def test_benford_compliant_data(self):
|
||||
"""符合本福特定律的数据应返回低风险"""
|
||||
# 生成符合本福特定律的数据
|
||||
random.seed(42)
|
||||
values = []
|
||||
for _ in range(200):
|
||||
# 对数均匀分布产生的数据符合本福特定律
|
||||
val = 10 ** (random.uniform(0, 4))
|
||||
values.append(f"{val:.2f}")
|
||||
result = benford_test(values)
|
||||
assert result['status'] == 'completed'
|
||||
# 对数均匀数据应近似符合
|
||||
assert result['conformity'] in ('close', 'acceptable', 'marginal')
|
||||
|
||||
def test_uniform_first_digit_high_risk(self):
|
||||
"""首位数字均匀分布应偏离本福特定律"""
|
||||
# 人为造假数据:首位数字接近均匀
|
||||
values = []
|
||||
for d in range(1, 10):
|
||||
for _ in range(20):
|
||||
values.append(f"{d}{random.randint(10,99)}")
|
||||
result = benford_test(values)
|
||||
assert result['status'] == 'completed'
|
||||
# 均匀分布会显著偏离本福特定律
|
||||
assert result['p_value'] < 0.05
|
||||
|
||||
def test_insufficient_data(self):
|
||||
"""数据量不足"""
|
||||
values = ['123', '456']
|
||||
result = benford_test(values)
|
||||
assert result['status'] == 'insufficient_data'
|
||||
|
||||
def test_first_digit_extraction(self):
|
||||
"""首位数字提取"""
|
||||
assert get_first_digit('314.15') == 3
|
||||
assert get_first_digit('0.0052') == 5
|
||||
assert get_first_digit('9999') == 9
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Test: GRIM Test
|
||||
# ============================================================
|
||||
|
||||
class TestGrimTest:
|
||||
"""GRIM 测试模块测试"""
|
||||
|
||||
def test_consistent_mean(self):
|
||||
"""合法均值应通过"""
|
||||
# n=20, 整数数据, mean=3.40 → sum=68 (整数) ✓
|
||||
result = grim_test_single('3.40', 20, decimals=2)
|
||||
assert result['consistent'] == True
|
||||
|
||||
def test_inconsistent_mean(self):
|
||||
"""非法均值应失败"""
|
||||
# n=20, 整数数据, mean=3.47 → sum=69.4 (非整数) ✗
|
||||
result = grim_test_single('3.47', 20, decimals=2)
|
||||
assert result['consistent'] == False
|
||||
|
||||
def test_consistent_mean_n25(self):
|
||||
"""n=25 的合法均值"""
|
||||
# n=25, mean=3.48 → sum=87 (整数) ✓
|
||||
result = grim_test_single('3.48', 25, decimals=2)
|
||||
assert result['consistent'] == True
|
||||
|
||||
def test_batch_mode(self):
|
||||
"""批量 GRIM 测试"""
|
||||
items = [
|
||||
{'mean': '3.40', 'n': 20, 'decimals': 2, 'label': 'Item A'},
|
||||
{'mean': '3.47', 'n': 20, 'decimals': 2, 'label': 'Item B'},
|
||||
{'mean': '4.00', 'n': 10, 'decimals': 2, 'label': 'Item C'},
|
||||
]
|
||||
result = grim_test_batch(items)
|
||||
assert result['status'] == 'completed'
|
||||
assert result['total_items'] == 3
|
||||
assert result['inconsistent_items'] == 1 # 3.47/20 不一致
|
||||
|
||||
def test_range_check(self):
|
||||
"""量表范围检查"""
|
||||
# 均值超出量表范围
|
||||
result = grim_test_single('6.50', 20, decimals=2, scale_min=1, scale_max=5)
|
||||
assert result.get('consistent') == False or result.get('status') == 'range_error'
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Test: Fixed Relation Test
|
||||
# ============================================================
|
||||
|
||||
class TestFixedRelationTest:
|
||||
"""固定关系检测模块测试"""
|
||||
|
||||
def test_exact_ratio_detected(self):
|
||||
"""精确固定比值应被检测到"""
|
||||
col1 = [1.23, 2.34, 3.45, 4.56, 5.67, 6.78, 7.89]
|
||||
col2 = [2.46, 4.68, 6.90, 9.12, 11.34, 13.56, 15.78] # ×2
|
||||
result = fixed_relation_test(col1, col2)
|
||||
assert result['status'] == 'completed'
|
||||
assert result['risk_level'] == 'high'
|
||||
assert result['risk_score'] >= 85
|
||||
assert result['detections']['fixed_ratio']['is_exact'] == True
|
||||
|
||||
def test_exact_difference_detected(self):
|
||||
"""精确固定差值应被检测到"""
|
||||
col1 = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]
|
||||
col2 = [4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] # +3
|
||||
result = fixed_relation_test(col1, col2)
|
||||
assert result['detections']['fixed_difference']['is_exact'] == True
|
||||
|
||||
def test_independent_data_low_risk(self):
|
||||
"""独立随机数据应返回低风险"""
|
||||
random.seed(42)
|
||||
col1 = [random.uniform(1, 10) for _ in range(30)]
|
||||
col2 = [random.uniform(1, 10) for _ in range(30)]
|
||||
result = fixed_relation_test(col1, col2)
|
||||
assert result['risk_level'] == 'low'
|
||||
assert result['risk_score'] < 30
|
||||
|
||||
def test_insufficient_data(self):
|
||||
"""数据不足"""
|
||||
result = fixed_relation_test([1.0, 2.0], [3.0, 4.0])
|
||||
assert result['status'] == 'insufficient_data'
|
||||
|
||||
def test_unequal_lengths(self):
|
||||
"""长度不一致应报错"""
|
||||
result = fixed_relation_test([1, 2, 3], [4, 5])
|
||||
assert result['status'] == 'error'
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Test: Decimal Consistency Test
|
||||
# ============================================================
|
||||
|
||||
class TestDecimalConsistencyTest:
|
||||
"""小数位一致性检测模块测试"""
|
||||
|
||||
def test_diverse_decimals_low_risk(self):
|
||||
"""多样化小数模式应低风险"""
|
||||
random.seed(42)
|
||||
values = [f"{random.uniform(1, 100):.{random.randint(1,4)}f}" for _ in range(50)]
|
||||
result = decimal_consistency_test(values)
|
||||
assert result['status'] == 'completed'
|
||||
assert result['risk_level'] in ('low', 'medium')
|
||||
|
||||
def test_repeated_decimals_high_risk(self):
|
||||
"""高度重复的小数模式应高风险"""
|
||||
# 所有值小数部分都是 .34
|
||||
values = [f"{i}.34" for i in range(1, 31)]
|
||||
result = decimal_consistency_test(values)
|
||||
assert result['status'] == 'completed'
|
||||
assert result['risk_score'] >= 25 # 至少中风险
|
||||
|
||||
def test_insufficient_data(self):
|
||||
"""数据不足"""
|
||||
values = ['1.23', '4.56']
|
||||
result = decimal_consistency_test(values)
|
||||
assert result['status'] == 'insufficient_data'
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Integration Test
|
||||
# ============================================================
|
||||
|
||||
class TestIntegration:
|
||||
"""集成测试:模拟完整检测流程"""
|
||||
|
||||
def test_fake_data_high_risk(self):
|
||||
"""已知造假数据应返回高风险"""
|
||||
# 模拟耿同学发现的典型造假:固定比例关系
|
||||
control = [2.34, 3.12, 1.87, 4.56, 2.98, 3.45, 1.23, 5.67, 2.01, 3.89]
|
||||
treatment = [x * 2 for x in control] # 精确 ×2
|
||||
|
||||
result = fixed_relation_test(control, treatment, 'Control', 'Treatment')
|
||||
assert result['risk_score'] >= 85
|
||||
assert 'fixed_ratio' in result['detections']
|
||||
assert result['detections']['fixed_ratio']['mean_ratio'] == 2.0
|
||||
|
||||
def test_real_data_low_risk(self):
|
||||
"""正常实验数据应返回低风险"""
|
||||
random.seed(123)
|
||||
# 模拟真实实验:基础值 + 效应 + 随机噪声
|
||||
control = [random.gauss(5, 1.5) for _ in range(20)]
|
||||
treatment = [x * random.gauss(2, 0.3) for x in control] # ×2 但有变异
|
||||
|
||||
result = fixed_relation_test(control, treatment, 'Control', 'Treatment')
|
||||
# 由于有随机噪声,不应该报告"精确"固定关系
|
||||
assert result['detections']['fixed_ratio']['is_exact'] == False
|
||||
|
||||
def test_output_schema_compliance(self):
|
||||
"""输出格式应符合标准 schema"""
|
||||
values = [f"{random.uniform(1, 100):.2f}" for _ in range(50)]
|
||||
result = last_digit_test(values)
|
||||
|
||||
# 必须包含的标准字段
|
||||
required_fields = ['test_name', 'status', 'risk_level', 'risk_score', 'interpretation']
|
||||
for field in required_fields:
|
||||
assert field in result, f"缺少必需字段: {field}"
|
||||
|
||||
# 风险评分范围
|
||||
assert 0 <= result['risk_score'] <= 100
|
||||
|
||||
# 风险等级合法值
|
||||
assert result['risk_level'] in ('low', 'medium', 'medium-high', 'high')
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Run tests
|
||||
# ============================================================
|
||||
|
||||
def run_all_tests():
|
||||
"""简易测试运行器(不依赖 pytest)"""
|
||||
import traceback
|
||||
|
||||
test_classes = [
|
||||
TestLastDigitTest,
|
||||
TestBenfordTest,
|
||||
TestGrimTest,
|
||||
TestFixedRelationTest,
|
||||
TestDecimalConsistencyTest,
|
||||
TestIntegration,
|
||||
]
|
||||
|
||||
total = 0
|
||||
passed = 0
|
||||
failed = 0
|
||||
errors = []
|
||||
|
||||
print("=" * 70)
|
||||
print(" 🧪 Geng Skill 单元测试")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
for test_class in test_classes:
|
||||
class_name = test_class.__name__
|
||||
print(f"▶ {class_name}")
|
||||
|
||||
instance = test_class()
|
||||
methods = [m for m in dir(instance) if m.startswith('test_')]
|
||||
|
||||
for method_name in methods:
|
||||
total += 1
|
||||
try:
|
||||
getattr(instance, method_name)()
|
||||
passed += 1
|
||||
print(f" ✅ {method_name}")
|
||||
except AssertionError as e:
|
||||
failed += 1
|
||||
errors.append((class_name, method_name, str(e)))
|
||||
print(f" ❌ {method_name}: {e}")
|
||||
except Exception as e:
|
||||
failed += 1
|
||||
errors.append((class_name, method_name, traceback.format_exc()))
|
||||
print(f" 💥 {method_name}: {type(e).__name__}: {e}")
|
||||
print()
|
||||
|
||||
print("=" * 70)
|
||||
print(f" 结果: {passed} 通过 / {failed} 失败 / {total} 总计")
|
||||
print("=" * 70)
|
||||
|
||||
if errors:
|
||||
print("\n❌ 失败详情:")
|
||||
for cls, method, err in errors:
|
||||
print(f" {cls}.{method}: {err[:200]}")
|
||||
|
||||
return failed == 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
success = run_all_tests()
|
||||
sys.exit(0 if success else 1)
|
||||
Reference in New Issue
Block a user