add pytest

This commit is contained in:
bridge
2025-12-18 22:08:06 +08:00
parent acf7d9dd35
commit 0890fc18b2
6 changed files with 130 additions and 117 deletions

56
tests/conftest.py Normal file
View File

@@ -0,0 +1,56 @@
import pytest
from unittest.mock import MagicMock
from src.classes.map import Map
from src.classes.tile import TileType
from src.classes.world import World
from src.classes.calendar import Month, Year, create_month_stamp
from src.classes.avatar import Avatar, Gender
from src.classes.age import Age
from src.classes.cultivation import Realm
from src.utils.id_generator import get_avatar_id
from src.classes.name import get_random_name
@pytest.fixture
def base_map():
"""创建一个 10x10 的全平原地图"""
width, height = 10, 10
game_map = Map(width=width, height=height)
for x in range(width):
for y in range(height):
game_map.create_tile(x, y, TileType.PLAIN)
return game_map
@pytest.fixture
def base_world(base_map):
"""创建一个基于 base_map 的世界,时间为 Year 1, Jan"""
return World(map=base_map, month_stamp=create_month_stamp(Year(1), Month.JANUARY))
from src.classes.root import Root
from src.classes.alignment import Alignment
@pytest.fixture
def dummy_avatar(base_world):
"""创建一个位于 (0,0) 的标准男性练气期角色"""
# 确保ID生成器重置或不冲突 (get_avatar_id 是随机UUID通常没问题)
av = Avatar(
world=base_world,
name="TestDummy",
id=get_avatar_id(),
birth_month_stamp=create_month_stamp(Year(2000), Month.JANUARY),
age=Age(20, Realm.Qi_Refinement),
gender=Gender.MALE,
pos_x=0,
pos_y=0,
root=Root.GOLD, # 固定灵根
personas=[], # 清空特质,避免随机效果
alignment=Alignment.RIGHTEOUS # 固定阵营
)
# 赋予一个 Mock 武器,防止 get_avatar_info 报错
av.weapon = MagicMock()
av.weapon.get_detailed_info.return_value = "测试木剑(凡品)"
av.weapon_proficiency = 0.0
return av

View File

@@ -1,32 +1,13 @@
from src.utils.id_generator import get_avatar_id
from src.classes.avatar import Avatar, Gender
from src.classes.calendar import Month, Year, MonthStamp, create_month_stamp
from src.classes.world import World
from src.classes.map import Map
from src.classes.tile import TileType
from src.classes.age import Age
from src.classes.cultivation import Realm
from src.classes.name import get_random_name
def test_basic():
from src.classes.avatar import Avatar
# test_basic is now simplified using fixtures
def test_basic(base_world, dummy_avatar):
"""
测试整个基础代码能不能run起来
使用 conftest.py 中的 fixtures 简化设置
"""
map = Map(width=2, height=2)
for x in range(2):
for y in range(2):
map.create_tile(x, y, TileType.PLAIN)
world = World(map=map, month_stamp=create_month_stamp(Year(1), Month.JANUARY))
avatar = Avatar(
world=world,
name=get_random_name(Gender.MALE),
id=get_avatar_id(),
birth_month_stamp=create_month_stamp(Year(2000), Month.JANUARY),
age=Age(20, Realm.Qi_Refinement),
gender=Gender.MALE
)
# fixtures 已经创建了 map, world, avatar
assert base_world.map.width == 10
assert base_world.map.height == 10
assert dummy_avatar.world == base_world
assert dummy_avatar.age.age == 20

View File

@@ -16,11 +16,11 @@ from src.sim.load.load_game import load_game
from src.utils.id_generator import get_avatar_id
from src.utils.config import CONFIG
# Helper to create a simple map
def create_simple_map():
m = Map(width=5, height=5) # Slightly larger to be safe
for x in range(5):
for y in range(5):
# Helper to create a simple map (aligned with conftest base_map logic)
def create_test_map():
m = Map(width=10, height=10)
for x in range(10):
for y in range(10):
m.create_tile(x, y, TileType.PLAIN)
return m
@@ -37,7 +37,7 @@ def test_save_load_cycle(temp_save_dir):
"""
# 1. Setup World
# Create a deterministic map for testing
game_map = create_simple_map()
game_map = create_test_map()
# Set a specific time
start_year = Year(100)
@@ -45,6 +45,7 @@ def test_save_load_cycle(temp_save_dir):
month_stamp = create_month_stamp(start_year, start_month)
world = World(map=game_map, month_stamp=month_stamp)
# 2. Add an Avatar
avatar_id = get_avatar_id()
@@ -92,7 +93,7 @@ def test_save_load_cycle(temp_save_dir):
# but since it's inside, we rely on sys.modules or patch the target module path.
# The import in load_game.py is: from src.run.load_map import load_cultivation_world_map
with patch('src.run.load_map.load_cultivation_world_map', return_value=create_simple_map()):
with patch('src.run.load_map.load_cultivation_world_map', return_value=create_test_map()):
# We also need to be careful about 'sects_by_id' if we had sects, but we don't.
loaded_world, loaded_sim, loaded_sects = load_game(save_path)
@@ -126,7 +127,7 @@ def test_save_load_with_relations(temp_save_dir):
"""
Test saving and loading avatars with relationships.
"""
game_map = create_simple_map()
game_map = create_test_map()
world = World(map=game_map, month_stamp=create_month_stamp(Year(1), Month.JANUARY))
# Create two avatars
@@ -148,7 +149,7 @@ def test_save_load_with_relations(temp_save_dir):
save_path = temp_save_dir / "test_relation.json"
save_game(world, sim, [], save_path)
with patch('src.run.load_map.load_cultivation_world_map', return_value=create_simple_map()):
with patch('src.run.load_map.load_cultivation_world_map', return_value=create_test_map()):
l_world, _, _ = load_game(save_path)
l_av1 = l_world.avatar_manager.avatars[av1.id]

View File

@@ -48,17 +48,13 @@ def test_auto_promote():
assert should_auto_promote(Realm.Qi_Refinement, Realm.Qi_Refinement) == False
def test_avatar_sect_rank_assignment():
def test_avatar_sect_rank_assignment(base_world):
"""测试avatar创建时宗门职位分配"""
from src.run.load_map import load_cultivation_world_map
game_map = load_cultivation_world_map()
world = World(
map=game_map,
month_stamp=MonthStamp(100 * 12),
)
# 使用 base_world fixture不需要 load_cultivation_world_map
# 创建多个avatar
avatars_dict = make_avatars(world, count=20, current_month_stamp=MonthStamp(100 * 12))
avatars_dict = make_avatars(base_world, count=20, current_month_stamp=MonthStamp(100 * 12))
avatars = list(avatars_dict.values())
# 检查所有有宗门的avatar都有职位
@@ -75,17 +71,11 @@ def test_avatar_sect_rank_assignment():
assert avatar.sect_rank is None, f"{avatar.name} 散修不应该有职位"
def test_patriarch_uniqueness():
def test_patriarch_uniqueness(base_world):
"""测试每个宗门只有一个掌门"""
from src.run.load_map import load_cultivation_world_map
game_map = load_cultivation_world_map()
world = World(
map=game_map,
month_stamp=MonthStamp(100 * 12),
)
# 创建足够多的avatar
avatars_dict = make_avatars(world, count=50, current_month_stamp=MonthStamp(100 * 12))
avatars_dict = make_avatars(base_world, count=50, current_month_stamp=MonthStamp(100 * 12))
avatars = list(avatars_dict.values())
# 统计每个宗门的掌门数量
@@ -102,16 +92,10 @@ def test_patriarch_uniqueness():
assert len(patriarchs) <= 1, f"宗门 {sect_id} 有多个掌门: {patriarchs}"
def test_sect_str_display():
def test_sect_str_display(base_world):
"""测试宗门信息显示"""
from src.run.load_map import load_cultivation_world_map
game_map = load_cultivation_world_map()
world = World(
map=game_map,
month_stamp=MonthStamp(100 * 12),
)
avatars_dict = make_avatars(world, count=20, current_month_stamp=MonthStamp(100 * 12))
avatars_dict = make_avatars(base_world, count=20, current_month_stamp=MonthStamp(100 * 12))
avatars = list(avatars_dict.values())
for avatar in avatars:
@@ -127,16 +111,10 @@ def test_sect_str_display():
assert rank_name in sect_str
def test_cultivation_breakthrough_promotion():
def test_cultivation_breakthrough_promotion(base_world):
"""测试突破境界后自动晋升"""
from src.run.load_map import load_cultivation_world_map
game_map = load_cultivation_world_map()
world = World(
map=game_map,
month_stamp=MonthStamp(100 * 12),
)
avatars_dict = make_avatars(world, count=10, current_month_stamp=MonthStamp(100 * 12))
avatars_dict = make_avatars(base_world, count=10, current_month_stamp=MonthStamp(100 * 12))
avatars = list(avatars_dict.values())
# 找一个练气期的宗门弟子
@@ -160,6 +138,7 @@ def test_cultivation_breakthrough_promotion():
assert target_avatar.sect_rank > old_rank
if __name__ == "__main__":
pytest.main([__file__, "-v"])

View File

@@ -1,54 +1,48 @@
import random
import asyncio
import pytest
from unittest.mock import patch, MagicMock
from src.sim.simulator import Simulator
from src.classes.avatar import Avatar, Gender
from src.classes.calendar import Month, Year, MonthStamp, create_month_stamp
from src.classes.world import World
from src.classes.map import Map
from src.classes.action.move_to_direction import MoveToDirection
from src.classes.tile import TileType
from src.classes.action import Move
from src.classes.name import get_random_name
from src.classes.age import Age
from src.classes.cultivation import Realm
from src.classes.action_runtime import ActionInstance
def test_simulator_step_moves_avatar_and_sets_tile(base_world, dummy_avatar):
# Set initial position
dummy_avatar.pos_x = 1
dummy_avatar.pos_y = 1
# Ensure tile is updated to initial position (fixture puts it at 0,0)
dummy_avatar.tile = base_world.map.get_tile(1, 1)
def test_simulator_step_moves_avatar_and_sets_tile():
# 固定随机种子,确保决定的移动是可预测的
random.seed(0)
sim = Simulator(base_world)
base_world.avatar_manager.avatars[dummy_avatar.id] = dummy_avatar
# 构建 3x3 地图并填充地块
game_map = Map(width=3, height=3)
for x in range(3):
for y in range(3):
game_map.create_tile(x, y, TileType.PLAIN)
# Manually assign a MoveToDirection action to avoid relying on LLM
action = MoveToDirection(dummy_avatar, base_world)
# "East" means x + 1
direction = "East"
action.start(direction=direction) # Initialize start_monthstamp etc.
# Wrap in ActionInstance
dummy_avatar.current_action = ActionInstance(action=action, params={"direction": direction})
world = World(map=game_map, month_stamp=create_month_stamp(Year(1), Month.JANUARY))
# Mock LLM to avoid external calls or errors
with patch("src.sim.simulator.llm_ai") as mock_ai:
mock_ai.decide = MagicMock(return_value={})
print(f"DEBUG: Before step: pos_x={dummy_avatar.pos_x}")
# Run step synchronously
asyncio.run(sim.step())
print(f"DEBUG: After step: pos_x={dummy_avatar.pos_x}")
print(f"DEBUG: move_step_length={getattr(dummy_avatar, 'move_step_length', 'Not set')}")
print(f"DEBUG: effects={dummy_avatar.effects}")
# 将角色放在地图中心,避免越界
avatar = Avatar(
world=world,
name=get_random_name(Gender.MALE),
id="1",
birth_month_stamp=create_month_stamp(Year(2000), Month.JANUARY),
age=Age(20, Realm.Qi_Refinement),
gender=Gender.MALE,
pos_x=1,
pos_y=1,
)
sim = Simulator(world)
world.avatar_manager.avatars["1"] = avatar
# 执行一步模拟
sim.step()
# 断言位置在边界内
assert 0 <= avatar.pos_x < game_map.width
assert 0 <= avatar.pos_y < game_map.height
# 断言 tile 已正确设置且与位置一致
assert avatar.tile is not None
assert avatar.tile.x == avatar.pos_x
assert avatar.tile.y == avatar.pos_y
# Assert moved East (x increased by move_step_length)
# Current move step for Qi Refinement is 2
assert dummy_avatar.pos_x == 3
assert dummy_avatar.pos_y == 1
# Assert tile is updated
assert dummy_avatar.tile is not None
assert dummy_avatar.tile.x == 3
assert dummy_avatar.tile.y == 1