테스트 강화 및 코드 품질 개선

This commit is contained in:
2025-12-17 00:01:46 +09:00
parent 37a150bd0d
commit 00c57ddd32
51 changed files with 10670 additions and 217 deletions

98
tests/test_v3_features.py Normal file
View File

@@ -0,0 +1,98 @@
import unittest
from unittest.mock import MagicMock, patch
from src.config import RuntimeConfig
from src.holdings import fetch_holdings_from_upbit
from src.order import execute_sell_order_with_confirmation, place_buy_order_upbit
class TestV3Features(unittest.TestCase):
def setUp(self):
self.mock_cfg = MagicMock(spec=RuntimeConfig)
self.mock_cfg.dry_run = False
self.mock_cfg.upbit_access_key = "test_key"
self.mock_cfg.upbit_secret_key = "test_secret"
self.mock_cfg.config = {
"auto_trade": {"min_order_value_krw": 5000},
"confirm": {"confirm_via_file": True, "confirm_stop_loss": False},
}
self.mock_cfg.telegram_parse_mode = "HTML"
self.mock_cfg.telegram_bot_token = "123:test_token"
self.mock_cfg.telegram_chat_id = "123456789"
@patch("src.common.krw_balance_lock")
@patch("src.order.pyupbit.Upbit")
def test_krw_balance_locking(self, mock_upbit_cls, mock_lock):
"""CRITICAL: KRW 잔고 확인 시 락이 제대로 동작하는지 테스트"""
mock_upbit = mock_upbit_cls.return_value
mock_upbit.get_balance.return_value = 10000
mock_upbit.buy_market_order.return_value = {"uuid": "test_uuid"}
place_buy_order_upbit("KRW-BTC", 5000, self.mock_cfg)
# 락의 __enter__ 메소드가 호출되었는지 확인 (with lock: 구문)
mock_lock.__enter__.assert_called()
@patch("src.order.send_telegram")
@patch("src.order.place_sell_order_upbit")
@patch("src.order._write_pending_order")
def test_immediate_stop_loss(self, mock_write_pending, mock_place_sell, mock_send_telegram):
"""HIGH: Stop Loss 시 파일 확인 없이 즉시 매도되는지 테스트"""
# 1. 일반 매도 (사유 없음) -> 파일 확인 필요
execute_sell_order_with_confirmation("KRW-BTC", 1.0, self.mock_cfg, reason="")
mock_write_pending.assert_called()
mock_place_sell.assert_not_called()
mock_write_pending.reset_mock()
mock_place_sell.reset_mock()
# 2. 손절 (confirm_stop_loss=False) -> 즉시 매도
execute_sell_order_with_confirmation("KRW-BTC", 1.0, self.mock_cfg, reason="stop_loss triggered")
mock_write_pending.assert_not_called()
mock_place_sell.assert_called_once()
mock_write_pending.reset_mock()
mock_place_sell.reset_mock()
# 3. 손절 설정 변경 (confirm_stop_loss=True) -> 확인 필요
self.mock_cfg.config["confirm"]["confirm_stop_loss"] = True
execute_sell_order_with_confirmation("KRW-BTC", 1.0, self.mock_cfg, reason="stop_loss triggered")
mock_write_pending.assert_called()
mock_place_sell.assert_not_called()
@patch("src.holdings.pyupbit.Upbit")
@patch("src.holdings._load_holdings_unsafe")
def test_robust_holdings_sync(self, mock_load, mock_upbit_cls):
"""HIGH: Holdings Sync 시 max_price가 보존되는지 테스트"""
mock_upbit = mock_upbit_cls.return_value
# API 잔고: BTC 현재가 5000만원, 매수가 4000만원
mock_upbit.get_balances.return_value = [
{"currency": "BTC", "balance": "1.0", "avg_buy_price_krw": "40000000"},
{"currency": "KRW", "balance": "1000000"}, # KRW는 무시됨
]
# 로컬 파일: 이미 max_price가 6000만원으로 기록되어 있음
mock_load.return_value = {
"KRW-BTC": {"buy_price": 40000000, "amount": 1.0, "max_price": 60000000, "partial_sell_done": True}
}
# 실행
synced_holdings = fetch_holdings_from_upbit(self.mock_cfg)
# 검증
self.assertIn("KRW-BTC", synced_holdings)
holding = synced_holdings["KRW-BTC"]
# 1. max_price가 로컬 값(6000만)으로 유지되어야 함 (초기화 안됨)
self.assertEqual(holding["max_price"], 60000000)
# 2. partial_sell_done 플래그도 유지되어야 함
self.assertTrue(holding["partial_sell_done"])
# 3. 매수가는 API 값이어야 함
self.assertEqual(holding["buy_price"], 40000000)
if __name__ == "__main__":
unittest.main()