9.4 KiB
9.4 KiB
チケット #014: Redditテスト実装
概要
Reddit praw実装の包括的なテストスイートの実装
目的
- 各コンポーネントの単体テスト
- 統合テストの実装
- モックを使用したAPI呼び出しのテスト
- 既存システムとの互換性テスト
実装要件
1. テスト優先開発(TDD)アプローチ
# テストを先に作成してから実装を行う
# 各モジュールの実装前にテストを定義
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 テスト
# 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 テスト
# 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 テスト
# 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. 統合テスト
# 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. モックデータ生成
# 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. 互換性テスト
# 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. パフォーマンステスト
@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)