TradingAgents/docs/014_Reddit_Testing_Implemen...

288 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# チケット #014: Redditテスト実装
## 概要
Reddit praw実装の包括的なテストスイートの実装
## 目的
- 各コンポーネントの単体テスト
- 統合テストの実装
- モックを使用したAPI呼び出しのテスト
- 既存システムとの互換性テスト
## 実装要件
### 1. テスト優先開発TDDアプローチ
```python
# テストを先に作成してから実装を行う
# 各モジュールの実装前にテストを定義
```
### 2. テスト構造
```
tests/
├── unit/
│ ├── test_reddit_praw_client.py
│ ├── test_reddit_data_fetcher.py
│ ├── test_reddit_cache_manager.py
│ └── test_reddit_utils_compatibility.py
├── integration/
│ ├── test_reddit_cli_commands.py
│ ├── test_reddit_full_flow.py
│ └── test_reddit_auto_update.py
├── fixtures/
│ ├── reddit_mock_data.py
│ └── reddit_test_config.py
└── conftest.py
```
### 3. RedditPrawClient テスト
```python
# tests/unit/test_reddit_praw_client.py
class TestRedditPrawClient:
@pytest.fixture
def mock_reddit(self):
"""prawのRedditオブジェクトをモック"""
with patch('praw.Reddit') as mock:
yield mock
def test_authentication_success(self, mock_reddit):
"""認証成功のテスト"""
client = RedditPrawClient(test_config)
assert client.authenticate() is True
def test_authentication_failure(self, mock_reddit):
"""認証失敗のテスト"""
mock_reddit.side_effect = Exception("Invalid credentials")
client = RedditPrawClient(invalid_config)
assert client.authenticate() is False
def test_get_subreddit_posts(self, mock_reddit):
"""subreddit投稿取得のテスト"""
# モックデータ設定
mock_posts = create_mock_posts(count=10)
mock_reddit.return_value.subreddit.return_value.hot.return_value = mock_posts
client = RedditPrawClient(test_config)
posts = client.get_subreddit_posts("worldnews", limit=10)
assert len(posts) == 10
assert all('title' in post for post in posts)
```
### 4. RedditDataFetcher テスト
```python
# tests/unit/test_reddit_data_fetcher.py
class TestRedditDataFetcher:
def test_fetch_global_news(self, mock_client):
"""グローバルニュース取得のテスト"""
fetcher = RedditDataFetcher(mock_client, test_config)
posts = fetcher.fetch_global_news("2024-01-01", limit_per_subreddit=5)
# 5 subreddits × 5 posts = 25 posts expected
assert len(posts) == 25
assert all(post['posted_date'] == "2024-01-01" for post in posts)
def test_company_news_filtering(self):
"""企業関連投稿のフィルタリングテスト"""
posts = [
{"title": "Apple announces new iPhone", "selftext": "..."},
{"title": "Random tech news", "selftext": "..."},
{"title": "AAPL stock rises", "selftext": "..."},
]
filtered = filter_company_relevant_posts(posts, "AAPL", "Apple")
assert len(filtered) == 2
def test_duplicate_removal(self):
"""重複排除のテスト"""
fetcher = RedditDataFetcher(mock_client, test_config)
posts_with_duplicates = [
{"id": "abc123", "title": "Post 1"},
{"id": "def456", "title": "Post 2"},
{"id": "abc123", "title": "Post 1"}, # 重複
]
unique_posts = fetcher.remove_duplicates(posts_with_duplicates)
assert len(unique_posts) == 2
```
### 5. RedditCacheManager テスト
```python
# tests/unit/test_reddit_cache_manager.py
class TestRedditCacheManager:
@pytest.fixture
def temp_cache_dir(self, tmp_path):
"""テスト用の一時ディレクトリ"""
return tmp_path / "reddit_data"
def test_save_and_load_posts(self, temp_cache_dir):
"""投稿の保存と読み込みテスト"""
manager = RedditCacheManager(str(temp_cache_dir))
test_posts = [
{"id": "1", "title": "Test Post 1"},
{"id": "2", "title": "Test Post 2"},
]
# 保存
file_path = manager.save_posts(
test_posts, "global_news", "2024-01-01", "worldnews"
)
assert Path(file_path).exists()
# 読み込み
loaded_posts = manager.load_posts(
"global_news", "2024-01-01", "worldnews"
)
assert len(loaded_posts) == 2
assert loaded_posts[0]["title"] == "Test Post 1"
def test_fetch_history_tracking(self, temp_cache_dir):
"""取得履歴の記録テスト"""
manager = RedditCacheManager(str(temp_cache_dir))
manager.update_fetch_history(
"global_news",
"2024-01-01",
["worldnews", "news"],
"2024-01-02T10:00:00Z",
100
)
history = manager.get_fetch_history()
assert "global_news" in history
assert history["global_news"]["2024-01-01"]["post_count"] == 100
```
### 6. 統合テスト
```python
# tests/integration/test_reddit_full_flow.py
@pytest.mark.integration
class TestRedditFullFlow:
def test_end_to_end_data_fetch(self):
"""完全なデータ取得フローのテスト"""
# 実際のAPIは使わず、モックで代替
with patch('praw.Reddit') as mock_reddit:
setup_mock_reddit_responses(mock_reddit)
# CLI実行
result = runner.invoke(cli, [
'reddit', 'fetch-historical',
'--no-interactive',
'--start', '2024-01-01',
'--end', '2024-01-01',
'--category', 'global_news'
])
assert result.exit_code == 0
# データが保存されたか確認
cache_manager = RedditCacheManager(test_data_dir)
posts = cache_manager.load_posts("global_news", "2024-01-01")
assert len(posts) > 0
```
### 7. モックデータ生成
```python
# tests/fixtures/reddit_mock_data.py
def create_mock_post(post_id: str = None, **kwargs):
"""モック投稿データの生成"""
post = {
"id": post_id or str(uuid.uuid4()),
"title": kwargs.get("title", "Test Post Title"),
"selftext": kwargs.get("selftext", "Test post content"),
"url": kwargs.get("url", "https://reddit.com/test"),
"ups": kwargs.get("ups", random.randint(1, 1000)),
"created_utc": kwargs.get("created_utc", int(time.time())),
"subreddit": kwargs.get("subreddit", "test"),
"author": kwargs.get("author", "test_user"),
"num_comments": kwargs.get("num_comments", random.randint(0, 100))
}
return post
def create_mock_posts(count: int = 10, **kwargs):
"""複数のモック投稿を生成"""
return [create_mock_post(f"post_{i}", **kwargs) for i in range(count)]
```
### 8. 互換性テスト
```python
# tests/unit/test_reddit_utils_compatibility.py
class TestRedditUtilsCompatibility:
def test_legacy_interface_maintained(self):
"""既存インターフェースが維持されているか"""
# 既存の関数シグネチャでの呼び出し
result = fetch_top_from_category(
"global_news",
"2024-01-01",
100,
data_path="test_data"
)
assert isinstance(result, list)
assert all(isinstance(post, dict) for post in result)
def test_data_format_conversion(self):
"""データ形式の変換テスト"""
praw_posts = create_mock_posts(5)
legacy_posts = convert_praw_to_legacy_format(praw_posts)
# 既存形式のフィールドが存在するか
for post in legacy_posts:
assert "content" in post # selftextから変換
assert "upvotes" in post # upsから変換
assert "posted_date" in post # created_utcから変換
```
### 9. パフォーマンステスト
```python
@pytest.mark.performance
def test_large_data_handling():
"""大量データ処理のパフォーマンステスト"""
start_time = time.time()
# 1000件のモックデータで処理
large_dataset = create_mock_posts(1000)
manager = RedditCacheManager(test_dir)
manager.save_posts(large_dataset, "test", "2024-01-01")
duration = time.time() - start_time
assert duration < 5.0 # 5秒以内に完了
```
## 受け入れ条件
- [ ] テスト優先開発TDDの実践
- [ ] 全モジュールの単体テスト実装
- [ ] 統合テストの成功
- [ ] コードカバレッジ80%以上
- [ ] モックを使用したAPI非依存テスト
- [ ] 既存システムとの互換性確認
- [ ] パフォーマンステストの合格
- [ ] USE_PRAW_APIフラグのテスト
## 依存関係
- pytest
- pytest-mock
- pytest-cov
- 全Reddit実装モジュール
## タスク
- [ ] テストディレクトリ構造の作成
- [ ] モックデータ生成ユーティリティの作成
- [ ] RedditPrawClientのテスト作成
- [ ] RedditDataFetcherのテスト作成
- [ ] RedditCacheManagerのテスト作成
- [ ] CLIコマンドのテスト作成
- [ ] 統合テストの実装
- [ ] 互換性テスト
- [ ] 段階的実装フラグのテスト
- [ ] パフォーマンステスト
- [ ] CI/CD設定GitHub Actions