テストデータ管理ガイド
[!NOTE] 最新の実装状況は 機能実装ステータス (Remaining Functionality) を参照してください。
概要
EvoSpikeNetプロジェクトでは、テストデータの管理に以下の戦略を採用しています:
- 動的生成: 小さく単純なテストデータはfixtureで動的生成
- Git LFS: 大きなテストデータはGit LFSで管理
- 自動セットアップ: conftest.pyでテスト環境を自動構築
テストデータの分類
動的生成(推奨)
対象: - ファイルサイズ < 1 MB - ランダムデータや単純なパターン - 生成時間 < 100ms - 再現性が不要なデータ
メリット: - リポジトリサイズが小さい - 常に最新の形式 - 依存関係なし - メンテナンスが容易
例:
@pytest.fixture
def dummy_audio_data(test_data_dir):
"""Generate dummy audio data dynamically."""
import wave
import struct
audio_file = test_data_dir / "generated_audio.wav"
# Generate 1 second of audio at 16kHz
sample_rate = 16000
duration = 1.0
frequency = 440.0 # A4 note
num_samples = int(sample_rate * duration)
# Generate sine wave
samples = []
for i in range(num_samples):
value = int(32767.0 * 0.3 * np.sin(2.0 * np.pi * frequency * i / sample_rate))
samples.append(value)
# Write WAV file
with wave.open(str(audio_file), 'w') as wav_file:
wav_file.setnchannels(1)
wav_file.setsampwidth(2)
wav_file.setframerate(sample_rate)
wav_file.writeframes(struct.pack('h' * len(samples), *samples))
return audio_file
Git LFS管理
対象: - ファイルサイズ > 1 MB - 実データやリアルなサンプル - 生成に時間がかかるデータ - 再現性が必要なデータ
メリット: - リアルなテストケース - 一貫性のあるテスト - CI/CDの高速化(生成不要)
デメリット: - LFSストレージが必要 - セットアップが必要 - 更新の管理が必要
例:
@pytest.fixture
def large_pretrained_model():
"""Load large pre-trained model from Git LFS."""
model_file = Path(__file__).parent / "data" / "large_model_weights.pt"
if not model_file.exists():
pytest.skip("Large model file not available. Run: git lfs pull")
import torch, inspect
load_kwargs = {"map_location": "cpu"}
try:
sig = inspect.signature(torch.load)
if "weights_only" in sig.parameters:
load_kwargs["weights_only"] = True
except Exception:
pass
weights = torch.load(model_file, **load_kwargs)
return weights
利用可能なFixture
セッションスコープ
test_data_dir
一時的なテストデータディレクトリを提供します。セッションごとに作成され、自動的にクリーンアップされます。
def test_example(test_data_dir):
# test_data_dirは Path オブジェクト
my_file = test_data_dir / "mydata.txt"
my_file.write_text("test content")
setup_test_environment
テスト環境を自動セットアップします(autouse=True)。以下のディレクトリを作成:
- models/: モデルファイル用
- artifacts/: アーティファクト用
- logs/: ログファイル用
- cache/: キャッシュファイル用
環境変数も設定:
- TEST_DATA_ROOT: テストデータのルートパス
- TEST_MODE: "true"に設定
関数スコープ
Audio/Video
dummy_audio_data(test_data_dir)
- 1秒間の440Hz正弦波(A4音)
- 16kHz サンプリングレート
- モノラル、16ビット
- WAV形式
def test_audio_processing(dummy_audio_data):
import wave
with wave.open(str(dummy_audio_data), 'r') as f:
assert f.getnchannels() == 1
assert f.getframerate() == 16000
Text
dummy_text_corpus(test_data_dir)
- 複数行のテキストコーパス
- UTF-8エンコーディング
- テキストファイルとして保存
def test_text_tokenization(dummy_text_corpus):
with open(dummy_text_corpus, 'r') as f:
text = f.read()
# テキスト処理のテスト
Images
dummy_image_data(test_data_dir)
- MNIST形式の画像(28x28 グレースケール)
- バッチサイズ: 4
- PyTorch tensor または NumPy array
- ファイルも保存(.pt または .npy)
@pytest.mark.requires_torch
def test_image_classification(dummy_image_data):
images = dummy_image_data['tensor']
assert images.shape == (4, 1, 28, 28)
Neural Data
dummy_spike_train(test_data_dir)
- 100ニューロン × 1000タイムステップ
- スパース(5%の発火確率)
- バイナリ(0/1)
- NumPy uint8形式
def test_spike_analysis(dummy_spike_train):
spikes = dummy_spike_train['data']
assert spikes.shape == (1000, 100)
assert spikes.dtype == np.uint8
dummy_embeddings(test_data_dir)
- 100サンプル × 128次元
- 正規化済みベクトル
- NumPy float32形式
def test_similarity_search(dummy_embeddings):
embeddings = dummy_embeddings['data']
# 各ベクトルは正規化されている
norms = np.linalg.norm(embeddings, axis=1)
assert np.allclose(norms, 1.0, atol=1e-5)
Structured Data
dummy_csv_data(test_data_dir)
- 簡単なCSVデータ
- ヘッダー付き
- 数値とカテゴリカルデータ
def test_data_loading(dummy_csv_data):
import pandas as pd
df = pd.read_csv(dummy_csv_data)
assert len(df) == 4
assert 'label' in df.columns
Model Weights
dummy_model_weights(test_data_dir)
- 簡単なニューラルネットワークの重み
- 2層(64→32ニューロン)
- PyTorch state_dict または NumPy dict
@pytest.mark.requires_torch
def test_model_loading(dummy_model_weights):
weights = dummy_model_weights['weights']
assert 'layer1.weight' in weights
assert weights['layer1.weight'].shape == (64, 128)
Multimodal
mock_multimodal_data(dummy_image_data, dummy_audio_data, dummy_text_corpus)
- 画像、音声、テキストを組み合わせ
- マルチモーダルテスト用
def test_multimodal_processing(mock_multimodal_data):
image = mock_multimodal_data['image']
audio = mock_multimodal_data['audio']
text = mock_multimodal_data['text']
# マルチモーダル処理のテスト
動的サイズ対応
large_test_dataset(test_data_dir, request)
- マーカーでサイズを指定可能
- Git LFS対応(ファイルがあれば使用)
- なければ動的生成
@pytest.mark.dataset_size("medium")
def test_with_medium_dataset(large_test_dataset):
data = large_test_dataset['data']
# dataは1000サンプル
assert len(data) == 1000
@pytest.mark.dataset_size("large")
def test_with_large_dataset(large_test_dataset):
data = large_test_dataset['data']
# dataは10000サンプル(Git LFSから読み込みまたは生成)
assert len(data) == 10000
Git LFSセットアップ
初期セットアップ
# Git LFSをインストール
brew install git-lfs # macOS
apt-get install git-lfs # Ubuntu/Debian
# リポジトリでGit LFSを有効化
git lfs install
# LFSファイルをダウンロード
git lfs pull
新しいファイルをLFSに追加
# 大きなテストデータを作成
python scripts/generate_test_data.py --size large --output tests/data/large_dataset_large.npy
# Git LFSで追跡
git lfs track "tests/data/large_dataset_large.npy"
# コミット(.gitattributesも一緒に)
git add .gitattributes tests/data/large_dataset_large.npy
git commit -m "Add large test dataset (Git LFS)"
git push
LFSファイルの確認
# LFSで追跡されているファイルを確認
git lfs ls-files
# LFSストレージの使用状況
git lfs status
ベストプラクティス
1. 小さいデータは動的生成
# ✅ 良い例: 動的生成
@pytest.fixture
def small_test_data():
return np.random.randn(100, 10)
# ❌ 悪い例: 小さいデータをファイルに保存
@pytest.fixture
def small_test_data():
return np.load("tests/data/small_data.npy") # 不要
2. 大きいデータはGit LFS
# ✅ 良い例: Git LFSから読み込み
@pytest.fixture
def large_pretrained_model():
model_file = Path(__file__).parent / "data" / "pretrained_model.pt"
if model_file.exists():
import inspect
load_kwargs = {"map_location": "cpu"}
try:
sig = inspect.signature(torch.load)
if "weights_only" in sig.parameters:
load_kwargs["weights_only"] = True
except Exception:
pass
return torch.load(model_file, **load_kwargs)
pytest.skip("Model not available")
# ❌ 悪い例: 大きいデータを動的生成(遅い)
@pytest.fixture
def large_pretrained_model():
# 毎回トレーニング(数分かかる)
return train_large_model()
3. テンポラリディレクトリを使用
# ✅ 良い例: test_data_dirを使用
def test_file_writing(test_data_dir):
output_file = test_data_dir / "output.txt"
output_file.write_text("test")
# 自動クリーンアップ
# ❌ 悪い例: カレントディレクトリに書き込み
def test_file_writing():
with open("output.txt", "w") as f:
f.write("test")
# クリーンアップ忘れ
4. Fixtureの再利用
# ✅ 良い例: 既存のfixtureを組み合わせ
@pytest.fixture
def prepared_dataset(dummy_image_data, test_data_dir):
images = dummy_image_data['tensor']
# 前処理
normalized = (images - images.mean()) / images.std()
return normalized
# ❌ 悪い例: すべて一から実装
@pytest.fixture
def prepared_dataset(test_data_dir):
# 画像生成から全部やり直し
images = torch.rand(4, 1, 28, 28) # dummy_image_dataと重複
normalized = (images - images.mean()) / images.std()
return normalized
5. 条件付きスキップ
# ✅ 良い例: LFSファイルがない場合はスキップ
@pytest.fixture
def real_world_data():
data_file = Path(__file__).parent / "data" / "real_data.npy"
if not data_file.exists():
pytest.skip("Real data not available (run 'git lfs pull')")
return np.load(data_file)
# ❌ 悪い例: ファイルがなければエラー
@pytest.fixture
def real_world_data():
data_file = Path(__file__).parent / "data" / "real_data.npy"
return np.load(data_file) # FileNotFoundError
CI/CDでの考慮事項
GitHub Actions
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout with LFS
uses: actions/checkout@v3
with:
lfs: true
- name: Pull LFS files (selective)
run: |
# 小さいファイルのみ取得(コスト削減)
git lfs pull --include="tests/data/small_*.npy"
- name: Run tests
run: |
# LFSファイルが必要なテストはスキップ
pytest -m "not requires_lfs" tests/
ローカル開発
# LFSなしでクローン(高速)
GIT_LFS_SKIP_SMUDGE=1 git clone <repo>
cd <repo>
# 必要なLFSファイルのみ取得
git lfs pull --include="tests/data/specific_file.npy"
# または全LFSファイルを取得
git lfs pull
トラブルシューティング
LFSファイルがダウンロードされない
# LFSファイルの状態確認
git lfs ls-files
# 強制的に再ダウンロード
git lfs fetch --all
git lfs checkout
テストが「file not found」で失敗
# Fixtureでスキップを実装
@pytest.fixture
def optional_large_data():
data_file = Path(__file__).parent / "data" / "large_data.npy"
if not data_file.exists():
pytest.skip("Large data not available. Run: git lfs pull")
return np.load(data_file)
ストレージクォータ超過
# 古いLFSファイルをクリーンアップ
git lfs prune
# 特定のファイルのみ追跡解除
git lfs untrack "tests/data/old_file.npy"
git rm tests/data/old_file.npy
git add .gitattributes
git commit -m "Remove old LFS file"