Ensures all tests have deterministic random behavior by setting random.seed(42) before each test. This prevents flaky tests caused by random number generation. Closes #44
213 lines
6.9 KiB
Python
213 lines
6.9 KiB
Python
import pytest
|
||
import random
|
||
from unittest.mock import MagicMock, AsyncMock, patch
|
||
|
||
from src.classes.map import Map
|
||
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def fixed_random_seed():
|
||
"""
|
||
Ensure all tests have deterministic random behavior.
|
||
This fixture is automatically applied to all tests.
|
||
"""
|
||
random.seed(42)
|
||
yield
|
||
from src.classes.tile import TileType, Tile
|
||
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
|
||
from src.classes.root import Root
|
||
from src.classes.alignment import Alignment
|
||
|
||
# Action related imports
|
||
from src.classes.elixir import Elixir, ElixirType
|
||
from src.classes.material import Material
|
||
from src.classes.weapon import Weapon
|
||
from src.classes.weapon_type import WeaponType
|
||
from src.classes.auxiliary import Auxiliary
|
||
from src.classes.region import CityRegion
|
||
|
||
@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))
|
||
|
||
@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
|
||
|
||
# 强制清空特质(因为 __post_init__ 会在 personas 为空时自动随机生成)
|
||
av.personas = []
|
||
# 强制清空功法,防止随机出的功法带有移动步长加成(如逍遥游)
|
||
av.technique = None
|
||
av.recalc_effects()
|
||
|
||
return av
|
||
|
||
@pytest.fixture
|
||
def mock_llm_managers():
|
||
"""
|
||
Mock 所有涉及 LLM 调用的管理器和函数,防止测试中意外调用 LLM。
|
||
"""
|
||
# 创建 mock LLM 配置
|
||
mock_llm_config = MagicMock()
|
||
mock_llm_config.api_key = "test_key"
|
||
mock_llm_config.base_url = "http://test.api/v1"
|
||
mock_llm_config.model_name = "test-model"
|
||
|
||
with patch("src.sim.simulator.llm_ai") as mock_ai, \
|
||
patch("src.sim.simulator.process_avatar_long_term_objective", new_callable=AsyncMock) as mock_lto, \
|
||
patch("src.classes.nickname.process_avatar_nickname", new_callable=AsyncMock) as mock_nick, \
|
||
patch("src.classes.relation_resolver.RelationResolver.run_batch", new_callable=AsyncMock) as mock_rr, \
|
||
patch("src.classes.history.HistoryManager.apply_history_influence", new_callable=AsyncMock) as mock_hist, \
|
||
patch("src.classes.story_teller.StoryTeller.tell_story", new_callable=AsyncMock) as mock_story, \
|
||
patch("src.utils.llm.config.LLMConfig.from_mode", return_value=mock_llm_config) as mock_config:
|
||
|
||
mock_ai.decide = AsyncMock(return_value={})
|
||
mock_lto.return_value = None
|
||
mock_nick.return_value = None
|
||
mock_rr.return_value = []
|
||
mock_hist.return_value = None
|
||
mock_story.return_value = "测试故事"
|
||
|
||
yield {
|
||
"ai": mock_ai,
|
||
"lto": mock_lto,
|
||
"nick": mock_nick,
|
||
"rr": mock_rr,
|
||
"hist": mock_hist,
|
||
"story": mock_story,
|
||
"config": mock_config
|
||
}
|
||
|
||
# --- Shared Helpers for Item Creation ---
|
||
|
||
def create_test_elixir(name, realm, price=100, elixir_id=1, effects=None):
|
||
if effects is None:
|
||
effects = {"max_hp": 10}
|
||
return Elixir(
|
||
id=elixir_id,
|
||
name=name,
|
||
realm=realm,
|
||
type=ElixirType.Breakthrough,
|
||
desc="测试丹药",
|
||
price=price,
|
||
effects=effects
|
||
)
|
||
|
||
def create_test_material(name, realm, material_id=101):
|
||
return Material(
|
||
id=material_id,
|
||
name=name,
|
||
desc="测试物品",
|
||
realm=realm
|
||
)
|
||
|
||
def create_test_weapon(name, realm, weapon_id=201):
|
||
return Weapon(
|
||
id=weapon_id,
|
||
name=name,
|
||
weapon_type=WeaponType.SWORD,
|
||
realm=realm,
|
||
desc="测试兵器",
|
||
effects={},
|
||
effect_desc=""
|
||
)
|
||
|
||
def create_test_auxiliary(name, realm, aux_id=301):
|
||
return Auxiliary(
|
||
id=aux_id,
|
||
name=name,
|
||
realm=realm,
|
||
desc="测试法宝",
|
||
effects={},
|
||
effect_desc=""
|
||
)
|
||
|
||
@pytest.fixture
|
||
def avatar_in_city(dummy_avatar):
|
||
"""
|
||
修改 dummy_avatar,使其位于城市中,并给予初始资金
|
||
"""
|
||
city_region = CityRegion(id=1, name="TestCity", desc="测试城市")
|
||
tile = Tile(0, 0, TileType.CITY)
|
||
tile.region = city_region
|
||
|
||
dummy_avatar.tile = tile
|
||
dummy_avatar.magic_stone = 1000
|
||
dummy_avatar.cultivation_progress.realm = Realm.Qi_Refinement
|
||
dummy_avatar.elixirs = []
|
||
dummy_avatar.materials = {} # 确保背包为空
|
||
dummy_avatar.weapon = None
|
||
dummy_avatar.auxiliary = None
|
||
|
||
return dummy_avatar
|
||
|
||
@pytest.fixture
|
||
def mock_item_data():
|
||
"""
|
||
提供标准的一组测试物品,包括材料、丹药、兵器、法宝。
|
||
返回一个包含这些对象的字典,方便后续 mock 使用。
|
||
"""
|
||
test_elixir = create_test_elixir("聚气丹", Realm.Qi_Refinement, price=100)
|
||
high_level_elixir = create_test_elixir("筑基丹", Realm.Foundation_Establishment, price=1000, elixir_id=2)
|
||
test_material = create_test_material("铁矿石", Realm.Qi_Refinement)
|
||
test_weapon = create_test_weapon("青云剑", Realm.Qi_Refinement)
|
||
test_auxiliary = create_test_auxiliary("聚灵珠", Realm.Qi_Refinement)
|
||
|
||
return {
|
||
"elixirs": {
|
||
"聚气丹": [test_elixir],
|
||
"筑基丹": [high_level_elixir]
|
||
},
|
||
"materials": {
|
||
"铁矿石": test_material
|
||
},
|
||
"weapons": {
|
||
"青云剑": test_weapon
|
||
},
|
||
"auxiliaries": {
|
||
"聚灵珠": test_auxiliary
|
||
},
|
||
# Direct access
|
||
"obj_elixir": test_elixir,
|
||
"obj_high_elixir": high_level_elixir,
|
||
"obj_material": test_material,
|
||
"obj_weapon": test_weapon,
|
||
"obj_auxiliary": test_auxiliary
|
||
}
|