テスト入門
テストを書くことで、バグを早期発見し、安心してコードを変更できるようになります。
テストの種類
単体テスト
関数やコンポーネント単位
- ・実行が速い
- ・問題の特定が簡単
- ・最も多く書く
結合テスト
複数の部品の連携
- ・API + DB
- ・コンポーネント間
- ・中程度の量
E2Eテスト
ユーザー視点の動作
- ・ブラウザ操作を再現
- ・実行が遅い
- ・重要なフローのみ
FastAPI のテスト(pytest)
セットアップ
pip install pytest pytest-asyncio httpx
tests/conftest.py
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from database import Base, get_db
from main import app
# テスト用DB
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
TestingSessionLocal = sessionmaker(bind=engine)
@pytest.fixture(scope="function")
def db():
Base.metadata.create_all(bind=engine)
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
Base.metadata.drop_all(bind=engine)
@pytest.fixture(scope="function")
def client(db):
def override_get_db():
yield db
app.dependency_overrides[get_db] = override_get_db
with TestClient(app) as c:
yield c
app.dependency_overrides.clear()
tests/test_posts.py
def test_create_post(client, db):
# ユーザー作成
client.post("/auth/register", json={
"email": "test@example.com",
"name": "Test",
"password": "password"
})
# ログイン
response = client.post("/auth/login", data={
"username": "test@example.com",
"password": "password"
})
token = response.json()["access_token"]
# 記事作成
response = client.post(
"/posts",
json={"title": "Test Post", "content": "Test Content"},
headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == 201
assert response.json()["title"] == "Test Post"
def test_get_posts(client):
response = client.get("/posts")
assert response.status_code == 200
assert isinstance(response.json(), list)
def test_get_post_not_found(client):
response = client.get("/posts/999")
assert response.status_code == 404
Next.js のテスト(Jest + RTL)
セットアップ
npm install -D jest @testing-library/react @testing-library/jest-dom jest-environment-jsdom
__tests__/Button.test.tsx
import { render, screen, fireEvent } from "@testing-library/react";
import { Button } from "@/components/ui/Button";
describe("Button", () => {
it("renders correctly", () => {
render(<Button>Click me</Button>);
expect(screen.getByText("Click me")).toBeInTheDocument();
});
it("calls onClick when clicked", () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click</Button>);
fireEvent.click(screen.getByText("Click"));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it("is disabled when disabled prop is true", () => {
render(<Button disabled>Disabled</Button>);
expect(screen.getByText("Disabled")).toBeDisabled();
});
});
__tests__/LoginForm.test.tsx
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { LoginForm } from "@/components/LoginForm";
// モック
jest.mock("@/contexts/AuthContext", () => ({
useAuth: () => ({
login: jest.fn().mockResolvedValue(undefined),
}),
}));
describe("LoginForm", () => {
it("submits form with email and password", async () => {
render(<LoginForm />);
fireEvent.change(screen.getByLabelText("メールアドレス"), {
target: { value: "test@example.com" },
});
fireEvent.change(screen.getByLabelText("パスワード"), {
target: { value: "password" },
});
fireEvent.click(screen.getByText("ログイン"));
await waitFor(() => {
// アサーション
});
});
it("shows error on invalid credentials", async () => {
// エラーケースのテスト
});
});
テスト実行
# FastAPI (pytest) pytest # 全テスト実行 pytest tests/test_posts.py # 特定ファイル pytest -v # 詳細表示 pytest --cov=. # カバレッジ # Next.js (jest) npm test # 全テスト実行 npm test -- --watch # ウォッチモード npm test -- --coverage # カバレッジ
AIにテストを書いてもらう
プロンプト例
以下の関数のテストを書いてください。 [関数のコード] テスト要件: - 正常系と異常系の両方 - エッジケースも考慮 - pytestを使用
まとめ
- ✓ 単体テストを中心に、重要なフローはE2Eも
- ✓ FastAPIはpytest + TestClient
- ✓ Next.jsはJest + React Testing Library
- ✓ AIにテストコードの生成を依頼できる