用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/ForceInjection/domain-driven-design-skills --skill testing-quality-assurance命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | Testing & Quality Assurance |
| description | Автоматизация тестирования и проверки качества кода |
| version | 2.0.0 |
| author | Family Budget Team |
| tags | ["testing","pytest","quality","coverage","linting","shared-budget"] |
| dependencies | ["api-development"] |
Автоматизация создания тестов и проверки качества кода для проекта Family Budget.
Используй этот скил когда нужно:
Скил автоматически вызывается при запросах типа:
Проект использует:
backend/tests/
├── unit/ # Unit тесты (изолированные)
│ ├── models/ # Тесты моделей
│ ├── services/ # Тесты сервисов (SCD2, JWT, etc.)
│ └── core/ # Тесты core модулей
├── integration/ # Integration тесты (с БД)
│ ├── test_auth_flow.py
│ ├── test_article_hierarchy.py
│ ├── test_scd_type2_versioning.py
│ └── test_user_isolation.py
├── e2e/ # End-to-end тесты
│ ├── test_user_journey.py
│ └── test_admin_journey.py
├── endpoints/ # API endpoint тесты
│ ├── test_auth.py
│ ├── test_articles.py
│ ├── test_facts.py
│ └── test_users.py
└── conftest.py # Fixtures и setup
bot/tests/
├── test_start_handler.py
├── test_add_handler.py
├── test_summary_handler.py
└── conftest.py
Создавай unit тесты для каждого endpoint со следующей структурой:
"""
Unit tests for {ModelName} endpoints.
Tests:
- CRUD operations (create, read, update, delete, list)
- User isolation
- SCD Type 2 versioning (for dimension tables)
- Error handling (404, 403, 401, 422)
- Input validation
"""
import pytest
from httpx import AsyncClient
from sqlmodel.ext.asyncio.session import AsyncSession
from backend.app.models.user import User
from backend.app.models.{model_name} import {ModelName}
@pytest.mark.asyncio
async def test_create_{model_name}_success(
client: AsyncClient,
test_user_token: str,
test_user: User,
):
"""Test creating a {model_name} successfully."""
payload = {
"name": "Test {ModelName}",
"description": "Test description",
}
response = await client.post(
"/api/v1/{model_name}s",
json=payload,
headers={"Authorization": f"Bearer {test_user_token}"},
)
assert response.status_code == 201
data = response.json()
assert data["name"] == "Test {ModelName}"
assert data["user_id"] == test_user.id
assert data["is_current"] is True
assert "id" in data
assert "created_at" in data
{model_name}_unauthorized(client: AsyncClient):
payload = {: }
response = client.post(
,
json=payload,
)
response.status_code ==
{model_name}_validation_error(
client: AsyncClient,
test_user_token: ,
):
payload = {
: ,
}
response = client.post(
,
json=payload,
headers={: },
)
response.status_code ==
{model_name}_success(
client: AsyncClient,
test_user_token: ,
test_{model_name}: {ModelName},
):
response = client.get(
,
headers={: },
)
response.status_code ==
data = response.json()
data[] == test_{model_name}.
data[] == test_{model_name}.name
{model_name}_not_found(
client: AsyncClient,
test_user_token: ,
):
response = client.get(
,
headers={: },
)
response.status_code ==
{model_name}_creates_new_version(
client: AsyncClient,
test_user_token: ,
test_{model_name}: {ModelName},
session: AsyncSession,
):
old_id = test_{model_name}.
old_name = test_{model_name}.name
payload = {: }
response = client.put(
,
json=payload,
headers={: },
)
response.status_code ==
data = response.json()
data[] != old_id
data[] ==
data[]
session.refresh(test_{model_name})
test_{model_name}. == old_id
test_{model_name}.name == old_name
test_{model_name}.is_current
test_{model_name}.valid_to.year !=
{model_name}_soft_delete(
client: AsyncClient,
test_user_token: ,
test_{model_name}: {ModelName},
session: AsyncSession,
):
response = client.delete(
,
headers={: },
)
response.status_code ==
session.refresh(test_{model_name})
test_{model_name}.is_current
{model_name}s_pagination(
client: AsyncClient,
test_user_token: ,
):
response = client.get(
,
headers={: },
)
response.status_code ==
data = response.json()
data
data
data
data
(data[], )
{model_name}(
client: AsyncClient,
test_user_token: ,
other_user_{model_name}: {ModelName},
):
response = client.get(
,
headers={: },
)
response.status_code ==
data = response.json()
data[] == other_user_{model_name}.
Для тестирования сложных workflow используй integration тесты:
"""
Integration test for {workflow_name}.
Tests complete workflow end-to-end with real database.
"""
import pytest
from httpx import AsyncClient
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession
from backend.app.models.user import User
from backend.app.models.article import Article
from backend.app.models.fact import BudgetFact
@pytest.mark.asyncio
async def test_{workflow_name}_complete_flow(
client: AsyncClient,
session: AsyncSession,
test_user_token: str,
test_user: User,
):
"""
Test complete {workflow_name} workflow.
Steps:
1. Create article
2. Create fact with article
3. Update fact
4. Verify SCD Type 2 versioning
5. Delete fact
6. Verify soft delete
"""
# Step 1: Create article
article_payload = {
"name": "Food",
"type": "expense",
"description": "Food expenses"
}
article_response = await client.post(
"/api/v1/articles",
json=article_payload,
headers={"Authorization": f"Bearer {test_user_token}"},
)
assert article_response.status_code == 201
article = article_response.json()
# Step 2: Create fact
fact_payload = {
"article_id": article["id"],
"amount": 100.50,
"date": ,
: ,
}
fact_response = client.post(
,
json=fact_payload,
headers={: },
)
fact_response.status_code ==
fact = fact_response.json()
update_payload = {: }
update_response = client.put(
,
json=update_payload,
headers={: },
)
update_response.status_code ==
updated_fact = update_response.json()
updated_fact[] ==
updated_fact[] == fact[]
delete_response = client.delete(
,
headers={: },
)
delete_response.status_code ==
get_response = client.get(
,
headers={: },
)
get_response.status_code ==
Создай reusable fixtures для тестов:
"""
Pytest fixtures for tests.
Provides:
- Database session
- Authenticated test client
- Test users with JWT tokens
- Test models (articles, facts, etc.)
"""
import asyncio
import pytest
from typing import AsyncGenerator
from httpx import AsyncClient
from sqlmodel import SQLModel
from sqlmodel.ext.asyncio.session import AsyncSession
from backend.app.db.session import engine, get_async_session
from backend.app.main import app
from backend.app.models.user import User
from backend.app.models.article import Article
from backend.app.services.jwt import create_access_token
@pytest.fixture(scope="session")
def event_loop():
"""Create event loop for async tests."""
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
@pytest.fixture(scope="function")
async def session() -> AsyncGenerator[AsyncSession, None]:
"""Create test database session."""
async with AsyncSession(engine) as session:
# Create tables
async with engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.create_all)
session
engine.begin() conn:
conn.run_sync(SQLModel.metadata.drop_all)
() -> AsyncGenerator[AsyncClient, ]:
():
session
app.dependency_overrides[get_async_session] = override_get_session
AsyncClient(app=app, base_url=) client:
client
app.dependency_overrides.clear()
() -> User:
user = User(
telegram_id=,
username=,
first_name=,
last_name=,
is_admin=,
is_active=,
)
session.add(user)
session.commit()
session.refresh(user)
user
() -> :
create_access_token(user_id=test_user.)
() -> User:
user = User(
telegram_id=,
username=,
first_name=,
last_name=,
is_admin=,
is_active=,
)
session.add(user)
session.commit()
session.refresh(user)
user
() -> :
create_access_token(user_id=admin_user.)
() -> Article:
article = Article(
user_id=test_user.,
name=,
=,
description=,
is_global=,
)
session.add(article)
session.commit()
session.refresh(article)
article
{model_name}(session: AsyncSession) -> {ModelName}:
other_user = User(
telegram_id=,
username=,
first_name=,
last_name=,
)
session.add(other_user)
session.flush()
{model_name_lower} = {ModelName}(
user_id=other_user.,
name=,
)
session.add({model_name_lower})
session.commit()
session.refresh({model_name_lower})
{model_name_lower}
# Backend тесты
cd backend
pytest
# Bot тесты
cd bot
pytest
# Only unit tests
pytest tests/unit
# Only integration tests
pytest tests/integration
# Only e2e tests
pytest tests/e2e
# Only specific endpoint tests
pytest tests/endpoints/test_articles.py
# Only specific test function
pytest tests/endpoints/test_articles.py::test_create_article_success
# Run with coverage
pytest --cov=backend --cov-report=html
# Open HTML report
open htmlcov/index.html
# Terminal coverage report
pytest --cov=backend --cov-report=term-missing
# Linting (ruff)
ruff check backend/
# Auto-fix linting issues
ruff check --fix backend/
# Formatting (black)
black backend/
# Check formatting without changes
black --check backend/
# Type checking (mypy)
mypy backend/
# All quality checks at once
ruff check backend/ && black --check backend/ && mypy backend/
После создания тестов проверь:
Создай unit тесты для endpoint /api/v1/articles.
Покрой все CRUD операции, user isolation, SCD Type 2 versioning.
Запусти все backend тесты с coverage отчетом.
Покажи файлы с coverage < 80%.
Проверь качество кода в backend/:
- Запусти ruff linting
- Проверь formatting (black)
- Запусти type checking (mypy)
Исправь найденные проблемы.
Q: Как мокировать API client в bot тестах?
A: Используй pytest-mock или unittest.mock:
@pytest.mark.asyncio
async def test_bot_handler(mocker):
mock_api = mocker.patch("bot.utils.api_client.get_api_client")
mock_api.return_value.list_articles.return_value = {"articles": [...]}
# Test handler
Q: Как тестировать SCD Type 2?
A: Проверяй что:
Q: Нужно ли тестировать каждый endpoint?
A: Да! Минимум: success case, 401 unauthorized, 404 not found, user isolation.