- 新增 finance/tests/ 6 个测试套件(agents/backtest/dao_upsert/factors/features/fundamental_lookahead) - 数据层: data_manager / dao 优化,新增 upsert 逻辑 - 因子层: 基本面因子抽象定位 _mapping、ROE/PE/PB 重构 - 回测层: vectorbt/engine 大改动(251 行),report 增强 - ML 层: features/backtest_integration 特征工程与回测优化 - CLI: agent_cli 重构 - config/settings 扩充配置项
69 lines
2.3 KiB
Python
69 lines
2.3 KiB
Python
"""
|
||
DAO 写库回归测试:验证 upsert(INSERT ... ON DUPLICATE KEY UPDATE)而非 pandas replace,
|
||
确保不 DROP 表、不丢失数据、DELETE+INSERT 不再分事务。
|
||
不使用真实 DB(用假 engine 捕获 SQL)。
|
||
"""
|
||
import sys
|
||
import os
|
||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
|
||
import pandas as pd
|
||
import pytest
|
||
from unittest.mock import patch
|
||
|
||
from database import dao
|
||
from database.models import StockDaily
|
||
from sqlalchemy.dialects import mysql
|
||
|
||
|
||
class _RecConn:
|
||
"""记录最近一次执行的 SQL。"""
|
||
def __init__(self):
|
||
self.sql = None
|
||
def begin(self):
|
||
return self
|
||
def __enter__(self):
|
||
return self
|
||
def __exit__(self, *a):
|
||
pass
|
||
def execute(self, stmt):
|
||
self.sql = str(stmt.compile(dialect=mysql.dialect(), compile_kwargs={"literal_binds": True}))
|
||
return type("R", (), {"rowcount": 2})()
|
||
|
||
|
||
class _RecEngine:
|
||
def __init__(self):
|
||
self.conn = _RecConn()
|
||
def begin(self):
|
||
return self.conn
|
||
|
||
|
||
def _daily_df():
|
||
return pd.DataFrame({
|
||
'ts_code': ['000001.SZ', '600519.SH'],
|
||
'trade_date': ['20260601', '20260601'],
|
||
'open': [10.1, 99.0], 'high': [10.8, 101.0], 'low': [10.0, 98.0],
|
||
'close': [10.5, 100.0], 'pre_close': [10.2, 99.5],
|
||
'change': [0.3, 0.5], 'pct_chg': [2.94, 0.50], 'vol': [100, 200],
|
||
'amount': [1050, 19900], 'turnover_rate': [0.5, 0.2],
|
||
})
|
||
|
||
|
||
def test_save_daily_generates_upsert_not_replace():
|
||
eng = _RecEngine()
|
||
with patch('database.dao.get_engine', return_value=eng):
|
||
dao.save_daily(_daily_df())
|
||
sql = eng.conn.sql
|
||
assert "INSERT INTO" in sql and "mac_stock_daily" in sql
|
||
assert "ON DUPLICATE KEY UPDATE" in sql, "save_daily 必须用 upsert,避免旧版 DELETE+INSERT"
|
||
# 确保不会走 pandas replace(整表重建)
|
||
assert "DROP TABLE" not in sql.upper()
|
||
|
||
|
||
def test_save_daily_no_multi_transaction():
|
||
"""save_daily 只构造一次 upsert 语句,DELETE 与 INSERT 合并为原子语句。"""
|
||
eng = _RecEngine()
|
||
with patch('database.dao.get_engine', return_value=eng):
|
||
dao.save_daily(_daily_df())
|
||
# 生成的 SQL 不含独立 DELETE,确保无"删除已发生但插入失败"的非原子风险
|
||
assert "DELETE" not in eng.conn.sql.upper() |