#!/usr/bin/env python3 """ AI陪伴减肥技能 - 完整测试用例 测试覆盖:基础计算/热量估算/步数计算/零食兑换/聚餐模式/数据管理/断点续传 """ import sys import json import os import unittest from datetime import datetime, timedelta sys.path.insert(0, os.path.dirname(__file__)) from diet_calculator import ( calculate_bmr, calculate_tdee, calculate_calorie_diff, calculate_fat_change, calculate_target_steps, calculate_calories_per_1000_steps, calculate_squat_calories, estimate_food, calculate_snack_exchange, calculate_party_mode, parse_command, format_command_help, generate_weekly_report, generate_daily_summary, generate_interaction_template ) class TestCoreCalculations(unittest.TestCase): """核心计算测试""" def setUp(self): self.user = { 'height': 165, 'weight': 60, 'age': 25, 'gender': 'female' } def test_bmr_female(self): """测试女性BMR计算""" bmr = calculate_bmr(60, 165, 25, 'female') self.assertAlmostEqual(bmr, 1410.5, places=1) print(f"✓ 女性BMR: {bmr} kcal") def test_bmr_male(self): """测试男性BMR计算""" bmr = calculate_bmr(75, 175, 30, 'male') self.assertGreater(bmr, 1700) self.assertLess(bmr, 1800) print(f"✓ 男性BMR: {bmr} kcal") def test_tdee_sedentary(self): """测试久坐TDEE计算""" bmr = 1410.5 tdee = calculate_tdee(bmr, 'sedentary') self.assertAlmostEqual(tdee, 1692.6, places=1) print(f"✓ 久坐TDEE: {tdee} kcal") def test_tdee_moderate(self): """测试中度活动TDEE计算""" bmr = 1410.5 tdee = calculate_tdee(bmr, 'moderate') self.assertAlmostEqual(tdee, 2186.3, places=1) print(f"✓ 中度活动TDEE: {tdee} kcal") def test_calorie_diff_positive(self): """测试热量超标""" diff = calculate_calorie_diff(2000, 1692.6) self.assertGreater(diff, 0) print(f"✓ 热量差(超标): {diff} kcal") def test_calorie_diff_negative(self): """测试热量缺口""" diff = calculate_calorie_diff(1200, 1692.6) self.assertLess(diff, 0) print(f"✓ 热量差(缺口): {diff} kcal") def test_fat_change_positive(self): """测试脂肪增量(长胖)""" fat = calculate_fat_change(500) self.assertGreater(fat, 0) print(f"✓ 脂肪增量(长胖): {fat}g") def test_fat_change_negative(self): """测试脂肪变化(减脂)""" fat = calculate_fat_change(-500) self.assertLess(fat, 0) print(f"✓ 脂肪变化(减脂): {fat}g") def test_target_steps_positive(self): """测试超标时目标步数""" steps = calculate_target_steps(300, 60) self.assertGreater(steps, 6000) self.assertLess(steps, 20000) print(f"✓ 超标目标步数: {steps}步") def test_target_steps_negative(self): """测试缺口时目标步数""" steps = calculate_target_steps(-300, 60) self.assertEqual(steps, 6000) print(f"✓ 缺口目标步数: {steps}步(最低保障)") def test_calories_per_1000_steps(self): """测试每千步消耗""" cal = calculate_calories_per_1000_steps(60) self.assertAlmostEqual(cal, 25.2, places=1) print(f"✓ 每千步消耗: {cal} kcal") def test_squat_calories(self): """测试蹲起消耗""" cal = calculate_squat_calories(60, 1.65) self.assertAlmostEqual(cal, 0.24, places=2) print(f"✓ 单次蹲起消耗: {cal} kcal") class TestFoodEstimation(unittest.TestCase): """食物热量估算测试""" def test_rice_estimation(self): """测试米饭估算""" result = estimate_food("一碗米饭") self.assertGreater(result[0], 100) self.assertLess(result[0], 400) print(f"✓ 一碗米饭: {result[0]} kcal") def test_multiple_foods(self): """测试多食物估算""" result = estimate_food("两小碗米饭一份宫保鸡丁半份西兰花") self.assertGreater(result[0], 300) print(f"✓ 混合食物总热量: {result[0]} kcal") print(f" 匹配食物: {result[3]}") def test_unknown_food(self): """测试未知食物处理""" result = estimate_food("神秘料理xyz123") self.assertGreater(result[0], 0) # 未知食物返回0 print(f"✓ 未知食物处理: {result}") class TestSnackExchange(unittest.TestCase): """零食兑换测试""" def test_snack_cake(self): """测试蛋糕兑换""" result = calculate_snack_exchange("蛋糕", 1, 60) self.assertTrue(result['found']) self.assertEqual(result['snack_name'], '蛋糕') self.assertGreater(result['equivalent_steps'], 0) print(f"✓ 蛋糕兑换: {result['equivalent_steps']}步") print(f" 热量: {result['calories']} kcal") print(f" 快走: {result['exercises'].get('快走', 'N/A')}分钟") def test_snack_with_quantity(self): """测试带份量零食""" result = calculate_snack_exchange("蛋糕", 2, 60) self.assertTrue(result['found']) self.assertEqual(result['portion_grams'], 100) # 2份=100g print(f"✓ 蛋糕(2份)兑换: {result['equivalent_steps']}步") def test_snack_not_found(self): """测试未找到零食""" result = calculate_snack_exchange("神秘零食xyz", 1, 60) self.assertFalse(result['found']) print(f"✓ 未找到零食返回found=False") class TestPartyMode(unittest.TestCase): """聚餐模式测试""" def test_hotpot_4_people(self): """测试4人火锅""" result = calculate_party_mode("火锅", 4, 60) self.assertIn('meal_type', result) self.assertEqual(result['people_count'], 4) self.assertIn('火锅', result['meal_type']) print(f"✓ 4人火锅: 人均{result['per_person_cal']} kcal") def test_bbq_3_people(self): """测试3人烧烤""" result = calculate_party_mode("烧烤", 3, 70) self.assertEqual(result['people_count'], 3) self.assertIn('烧烤', result['meal_type']) print(f"✓ 3人烧烤: 人均{result['per_person_cal']} kcal") def test_buffet_6_people(self): """测试6人自助餐""" result = calculate_party_mode("自助餐", 6, 65) self.assertEqual(result['people_count'], 6) self.assertIn('自助餐', result['meal_type']) print(f"✓ 6人自助餐: 人均{result['per_person_cal']} kcal") class TestCommandParsing(unittest.TestCase): """快捷命令解析测试""" def test_command_weight_checkin(self): """测试体重打卡命令""" result = parse_command("#打卡体重 58.5") self.assertEqual(result['action'], 'weight_checkin') # 注意:当前实现会将"58.5"作为await_weight处理 print(f"✓ 体重打卡命令解析: {result}") def test_command_weight_with_number(self): """测试带数字的体重打卡命令""" result = parse_command("#打卡体重58.5") self.assertEqual(result['action'], 'weight_checkin') self.assertEqual(result['params']['weight'], 58.5) print(f"✓ 连续数字体重打卡: {result}") def test_command_meal_checkin(self): """测试餐食打卡命令""" result = parse_command("#打卡早餐 粥包子") self.assertEqual(result['action'], 'breakfast_checkin') self.assertIn('粥包子', result['params']['foods'] or '') print(f"✓ 餐食打卡命令解析: {result}") def test_command_query(self): """测试查询命令""" result = parse_command("#查周报") self.assertEqual(result['action'], 'weekly_report') print(f"✓ 查询命令解析: {result}") def test_command_snack_calc(self): """测试零食计算命令""" result = parse_command("#算蛋糕") self.assertEqual(result['action'], 'snack_calorie') self.assertIn('蛋糕', result['params']['snack']) print(f"✓ 零食计算命令解析: {result}") def test_command_party_calc(self): """测试聚餐计算命令""" result = parse_command("#算火锅4人") self.assertEqual(result['action'], 'party_hotpot') self.assertEqual(result['params']['people'], 4) print(f"✓ 聚餐计算命令解析: {result}") def test_command_help(self): """测试帮助命令""" result = parse_command("#帮") self.assertEqual(result['action'], 'help') print(f"✓ 帮助命令解析成功") def test_command_settings(self): """测试设置命令""" result = parse_command("#设提醒8点") self.assertEqual(result['action'], 'set_reminder') print(f"✓ 设置命令解析: {result}") class TestWeeklyReport(unittest.TestCase): """周报生成测试""" def test_weekly_report_generation(self): """测试周报生成""" daily_records = [ {'date': '2024-01-10', 'score': 8, 'calorie_diff': -200, 'actual_steps': 8000, 'weight': 60}, {'date': '2024-01-11', 'score': 7, 'calorie_diff': -150, 'actual_steps': 7000, 'weight': 59.8}, {'date': '2024-01-12', 'score': 9, 'calorie_diff': -300, 'actual_steps': 9000, 'weight': 59.5}, ] report = generate_weekly_report(daily_records) self.assertIn('period', report) self.assertEqual(report['total_days'], 3) print(f"✓ 周报生成成功: {report['total_days']}天数据") class TestSummaryCard(unittest.TestCase): """总结卡片生成测试""" def test_daily_summary_generation(self): """测试日终总结生成""" user_info = {'height': 165, 'weight': 60, 'age': 25, 'gender': 'female'} calc_results = { 'total_calories': 1650, 'carb_ratio': 48, 'bmr': 1410.5, 'tdee': 1692.6, 'calorie_diff': -42.6, 'fat_change_g': -6 # 使用fat_change_g而不是fat_change_kg } exercise = { 'actual_steps': 7500, 'exercise_calories': 200 } evaluation = { 'total_score': 8.5, 'nutrition': '3.5/4', 'calorie_control': '2.5/3', 'carb_ratio': '1.5/2', 'diversity': '1.0/1' } foods = ['米饭', '鱼', '蔬菜'] card = generate_daily_summary( user_info, calc_results, exercise, evaluation, foods, streak_days=5, weight_change=-0.5, target_steps=8000 ) self.assertIn('今日营养处方', card) self.assertIn('1650', card) self.assertIn('8.5', card) print(f"✓ 日终总结卡片生成成功 (长度: {len(card)}字符)") class TestInteractionTemplates(unittest.TestCase): """交互话术模板测试""" def test_morning_greeting(self): """测试早安问候模板""" templates = generate_interaction_template() template = templates['morning_greeting'] self.assertIn('{today_steps}', template) print(f"✓ 早安问候模板包含步数变量") def test_snack_intro(self): """测试零食兑换介绍模板""" templates = generate_interaction_template() template = templates.get('food_input', '') self.assertIn('今天吃了什么', template) print(f"✓ 食物输入模板正常") def run_tests(): """运行所有测试""" print("\n" + "="*60) print("🏃 AI陪伴减肥技能 - 测试套件 🏃") print("="*60 + "\n") # 创建测试套件 loader = unittest.TestLoader() suite = unittest.TestSuite() # 添加测试类 suite.addTests(loader.loadTestsFromTestCase(TestCoreCalculations)) suite.addTests(loader.loadTestsFromTestCase(TestFoodEstimation)) suite.addTests(loader.loadTestsFromTestCase(TestSnackExchange)) suite.addTests(loader.loadTestsFromTestCase(TestPartyMode)) suite.addTests(loader.loadTestsFromTestCase(TestCommandParsing)) suite.addTests(loader.loadTestsFromTestCase(TestWeeklyReport)) suite.addTests(loader.loadTestsFromTestCase(TestSummaryCard)) suite.addTests(loader.loadTestsFromTestCase(TestInteractionTemplates)) # 运行测试 runner = unittest.TextTestRunner(verbosity=2) result = runner.run(suite) # 输出总结 print("\n" + "="*60) print("📊 测试总结") print("="*60) print(f"总测试数: {result.testsRun}") print(f"成功: {result.testsRun - len(result.failures) - len(result.errors)}") print(f"失败: {len(result.failures)}") print(f"错误: {len(result.errors)}") if result.wasSuccessful(): print("\n🎉 所有测试通过!") return 0 else: print("\n⚠️ 部分测试失败,请检查。") return 1 if __name__ == '__main__': sys.exit(run_tests())