Token导航 LogoToken导航TokenDH.com
效率external-serviceclawhub未标认证来源可访问clear审计通过

clean-pytest干净的 pytest

Agent Skill

用于辅助 Python 项目开发、测试、依赖管理和常见框架工作流。它适合让 Agent 阅读 Python 代码、定位测试问题、整理运行命令、生成脚本或分析数据处理逻辑。使用时需要确认项目虚拟环境、依赖版本和测试入口;涉及执行脚本、读写文件、访问数据库或调用外部 API 时,应先明确运行目录和输入输出范围,避免误改生产数据。

总安装

27,132

周安装

1,087

GitHub Stars

公开资料未说明

下载量

8,783
OpenClaw

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

MIT-0

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:clean-pytest(干净的 pytest)
来源仓库:https://github.com/marcoracer/clean-pytest
安装命令:
openclaw skills install clean-pytest
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 OpenClaw 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

ClawHubOpenClaw
openclaw skills install clean-pytest

简介

clean-pytest 指导使用 Fake 测试与契约测试编写可维护的 Python 测试用例。

  • 适合在 OpenClaw 中为 pytest 项目建立清晰测试套件时使用。
  • 通过 clawhub 安装,命令为 openclaw skills install clean-pytest。
  • 使用前需确认项目依赖与环境配置,注意避免测试污染生产数据。
  • 建议结合来源仓库和 README 文档进一步核验具体功能和使用细节。

SKILL.md

name
clean-pytest
description
Write clean, maintainable pytest tests using Fake-based testing, contract testing, and dependency injection patterns. Use when setting up test suites for Python/MCP projects, creating Fakes for external dependencies, writing contract tests, or implementing test patterns with fixtures and parametrization.
license
MIT
metadata
emoji
🧪
homepage
https://github.com/numinstante/skills
os
[darwin, linux, windows]
tags
[python, pytest, testing, tdd, mcp, contract-testing]
requires
anyBins
[python3]

Clean Pytest

Clean, maintainable pytest test patterns using Fake-based testing, contract testing, and dependency injection. Focuses on test isolation, reusability, and clarity through explicit AAA pattern and well-structured fixtures.

When to Use

  • Setting up test suites for Python/MCP projects
  • Creating Fake implementations for external dependencies
  • Writing contract tests for MCP tools/controllers
  • Implementing test patterns with dependency injection
  • Testing layered architectures (Controllers → Services → Repositories)
  • Writing parametrized tests for multiple scenarios

Core Principles

1. Fakes over Mocks

Use Fake classes instead of mocking with unittest.mock. Fakes are in-memory implementations that mimic real dependencies without external calls.

Why Fakes?

  • More readable and maintainable
  • Easier to debug
  • Better test isolation
  • No monkey-patching magic
  • Self-documenting behavior

2. Explicit AAA Pattern

Structure every test into three clear phases with comments:

# Arrange
# Set up test data and dependencies

# Act
# Execute the code under test

# Assert
# Verify the result

3. Dependency Injection in Fixtures

Inject dependencies between fixtures to maintain relationships and avoid duplication.

4. Contract Testing

Verify that components register tools/functions correctly and pass expected arguments.

Architecture Pattern

Controller (MCP Tools)
    ↓
Service (Business Logic)
    ↓
Repository (Data Access)
    ↓
Fake (Test Implementation)

Creating Fakes

Basic Fake Structure

Create a Fake class that implements the same interface as the real dependency:

# tests/fakes.py
from typing import Any, Dict, List, Optional

class FakeAuth:
    """Fake implementation of AuthProvider for testing."""
    def __init__(self) -> None:
        self.created: List[Dict[str, Any]] = []
        self.deleted: List[str] = []
        self._seq = 0
        self.fail_on_create: bool = False

    def create_user(self, email: str, password: str, display_name: str) -> str:
        if self.fail_on_create:
            raise RuntimeError("create_user failed (fake)")
        self._seq += 1
        uid = f"uid-{self._seq}"
        rec = {"uid": uid, "email": email, "display_name": display_name}
        self.created.append(rec)
        return uid

    def delete_user(self, uid: str) -> None:
        self.deleted.append(uid)

Repository Fake

class FakeUsersRepo:
    """Fake implementation of UsersRepository."""
    def __init__(self) -> None:
        self.users: Dict[str, Dict[str, Any]] = {}
        self.fail_on_upsert: bool = False

    def upsert_user_doc(self, uid: str, data: Dict[str, Any]) -> None:
        if self.fail_on_upsert:
            raise RuntimeError("upsert_user_doc failed (fake)")
        self.users[uid] = dict(data)

    def list_users(self, limit: Optional[int] = None) -> List[Dict[str, Any]]:
        items = list(self.users.values())
        if limit and limit > 0:
            items = items[:limit]
        return [dict(it) for it in items]

Controlled Failure Fakes

class FakeAuth:
    def __init__(self) -> None:
        self.fail_on_create: bool = False  # Control failure in tests

    def create_user(self, email: str, password: str, display_name: str) -> str:
        if self.fail_on_create:
            raise RuntimeError("create_user failed (fake)")
        # ... rest of implementation

Nested Repository Fakes

class FakeSectorsRepo:
    def __init__(self, institutions: FakeInstitutionsRepo | None = None) -> None:
        self.institutions = institutions  # Inject dependency
        self.data: Dict[str, Dict[str, Dict[str, Any]]] = {}

    def institution_exists(self, institution_id: str) -> bool:
        return bool(self.institutions and institution_id in self.institutions.data)

    def upsert_sector(self, institution_id: str, sector_id: str, data: Dict[str, Any]) -> None:
        self.data.setdefault(institution_id, {})[sector_id] = dict(data)

Fixtures

Basic Fixture (conftest.py)

# tests/conftest.py
import pytest
from tests.fakes import FakeAuth, FakeUsersRepo

@pytest.fixture()
def fake_auth():
    """Provide a fresh FakeAuth for each test."""
    return FakeAuth()

@pytest.fixture()
def fake_users_repo():
    """Provide a fresh FakeUsersRepo for each test."""
    return FakeUsersRepo()

Fixture with Dependency Injection

@pytest.fixture()
def fake_sectors_repo(fake_institutions_repo):
    """FakeSectorsRepo depends on FakeInstitutionsRepo."""
    return FakeSectorsRepo(institutions=fake_institutions_repo)

@pytest.fixture()
def fake_rooms_repo(fake_sectors_repo):
    """FakeRoomsRepo depends on FakeSectorsRepo."""
    return FakeRoomsRepo(sectors=fake_sectors_repo)

Environment Fixture

@pytest.fixture()
def user_env(fake_auth, fake_users_repo):
    """Provide service and all dependencies for user operations."""
    from myapp.services.user_service import UserService
    svc = UserService(fake_auth, fake_users_repo)
    return svc, fake_auth, fake_users_repo

Seeded Environment Fixture

@pytest.fixture()
def user_env_seeded(user_env):
    """Environment with pre-seeded data."""
    svc, auth, repo = user_env
    svc.add_user(email="test@example.com", password="secret", name="Test User")
    return svc

Fixture with Cleanup

@pytest.fixture()
def temp_file():
    """Provide a temporary file and clean up after test."""
    import tempfile
    import os
    fd, path = tempfile.mkstemp()
    os.close(fd)
    yield path
    os.unlink(path)

Service Layer Testing

Basic AAA Pattern Test

# tests/test_user_service.py
import pytest
from myapp.services.user_service import UserService

def test_add_user_success(fake_auth, fake_users_repo):
    # Arrange
    svc = UserService(fake_auth, fake_users_repo)
    email = "test@example.com"
    password = "secret"
    name = "Test User"

    # Act
    result = svc.add_user(email=email, password=password, name=name)

    # Assert
    assert result["status"] == "ok"
    assert result["user"]["email"] == email
    assert result["user"]["name"] == name
    assert result["uid"] in fake_users_repo.users

Parametrized Tests

@pytest.mark.parametrize(
    "email,password,name,role",
    [
        ("a@example.com", "secret", "Alice", "admin"),
        ("b@example.com", "p@ss", "Bob", "user"),
    ],
)
def test_add_user_parametrized(user_env, email, password, name, role):
    svc, _auth, _repo = user_env

    # Act
    res = svc.add_user(email=email, password=password, name=name, global_role=role)

    # Assert
    assert res["status"] == "ok"
    assert res["user"]["email"] == email
    assert res["user"]["name"] == name
    assert res["user"]["globalRole"] == role

Testing Error Scenarios with Fakes

@pytest.mark.parametrize("email", ["c@example.com", "d@example.com"])
def test_add_user_rollback_on_firestore_failure(fake_auth, fake_users_repo, email):
    # Arrange
    fake_users_repo.fail_on_upsert = True
    svc = UserService(fake_auth, fake_users_repo)

    # Act & Assert
    with pytest.raises(RuntimeError):
        svc.add_user(email=email, password="secret", name="Bob")

    # Assert rollback
    assert fake_auth.deleted, "Expected auth user to be deleted on Firestore failure"

Testing Timestamp Normalization

def test_list_users_normalizes_timestamps_to_iso(user_env):
    # Arrange
    svc, _auth, repo = user_env
    from datetime import datetime
    repo.users["u1"] = {
        "id": "u1",
        "email": "x@y.z",
        "name": "X",
        "globalRole": "user",
        "createdAt": datetime(2024, 1, 1),
        "updatedAt": datetime(2024, 1, 2),
    }

    # Act
    res = svc.list_users(limit=10)

    # Assert
    assert res["status"] == "ok"
    assert res["count"] == 1
    user = res["users"][0]
    assert isinstance(user["createdAt"], str)
    assert isinstance(user["updatedAt"], str)

Contract Testing

MCP Tool Registration Contract

Test that controllers properly register tools with expected signatures:

# tests/test_controllers_contract.py
from typing import Any, Callable, Dict

class FakeMCP:
    """Minimal FakeMCP for contract testing."""
    def __init__(self) -> None:
        self.tools: Dict[str, Callable[..., Any]] = {}
        self.meta: Dict[str, Dict[str, Any]] = {}

    def tool(self, name: str, description: str, tags: Optional[set] = None, meta: Optional[dict] = None):
        def decorator(fn: Callable[..., Any]):
            self.tools[name] = fn
            self.meta[name] = {
                "description": description,
                "tags": set(tags or set()),
                "meta": dict(meta or {}),
            }
            return fn
        return decorator


class FakeUserService:
    """Simple fake service that records calls."""
    def __init__(self):
        self.calls = []

    def add_user(self, **kwargs):
        self.calls.append(("add_user", kwargs))
        return {"status": "ok", "op": "add_user", "args": kwargs}


def test_users_controller_contract():
    # Arrange
    from myapp.controllers.users_controller import UsersController
    fake = FakeMCP()
    svc = FakeUserService()
    UsersController(fake, svc)

    # Assert tool registration
    assert "add_user" in fake.tools
    assert "list_users" in fake.tools

    # Act & Assert tool behavior
    res = fake.tools["add_user"](
        email="a@x.y", password="s3cr3t", name="Alice", global_role="admin"
    )
    assert res["status"] == "ok"
    assert res["op"] == "add_user"
    assert res["args"]["email"] == "a@x.y"

Parametrized Contract Tests

@pytest.mark.parametrize(
    "email,password,name,role",
    [
        ("a@x.y", "s3cr3t", "Alice", "admin"),
        ("b@x.y", "p@ssw0rd", "Bob", "user"),
    ],
)
def test_users_add_user_parametrized(_users_env, email, password, name, role):
    # Arrange
    fake, _ = _users_env

    # Act
    res = fake.tools["add_user"](
        email=email, password=password, name=name, global_role=role
    )

    # Assert
    assert res["status"] == "ok"
    assert res["op"] == "add_user"
    assert res["args"]["email"] == email

Repository Layer Testing

Testing Repository Operations

@pytest.fixture()
def repo_env(fake_institutions_repo, fake_sectors_repo):
    # Seed data
    fake_institutions_repo.upsert("inst1", {"id": "inst1", "name": "Inst One"})
    fake_sectors_repo.upsert_sector(
        "inst1", "er", {"id": "er", "name": "ER", "slug": "er", "isActive": True}
    )
    return fake_sectors_repo

Testing Multiple Data Scenarios

@pytest.mark.parametrize("rooms", [
    ["101"],
    ["201", {"name": "102", "id": "room-102"}],
])
def test_add_and_list_rooms(room_env, rooms):
    svc, _ = room_env

    # Act
    res = svc.add_sector_rooms("inst1", "er", rooms)

    # Assert
    assert res["status"] == "ok"
    assert res["count"] == len(rooms)

    lst = svc.list_sector_rooms("inst1", "er", limit=10)
    assert lst["status"] == "ok"
    assert lst["count"] == len(rooms)

Testing Limit Behavior

@pytest.mark.parametrize("limit", [1, 3])
def test_list_rooms_limits(room_env_seeded, limit):
    svc = room_env_seeded

    # Act
    lst = svc.list_sector_rooms("inst1", "er", limit=limit)

    # Assert
    assert lst["status"] == "ok"
    assert lst["count"] == min(2, limit)  # 2 items seeded

Testing Not Found Scenarios

@pytest.mark.parametrize("room_id,deleted", [
    ("room-102", True),
    ("room-999", False),
])
def test_remove_rooms_parametrized(room_env_seeded, room_id, deleted):
    svc = room_env_seeded

    # Act
    res = svc.remove_sector_room("inst1", "er", room_id)

    # Assert
    assert res["deleted"] is deleted
    if not deleted:
        assert res.get("reason") == "room_not_found"

Integration Testing

Conditional Integration Tests

Skip integration tests when external dependencies are not available:

# tests/test_integration_wiring.py
import os
import pytest

# Gate this integration test on presence of credentials
_ENV_KEYS = (
    "FIREBASE_SERVICE_ACCOUNT",
    "GOOGLE_APPLICATION_CREDENTIALS",
)
_has_env_creds = any(os.getenv(k) for k in _ENV_KEYS)

pytestmark = [
    pytest.mark.integration,
    pytest.mark.skipif(
        not _has_env_creds,
        reason=(
            "Integration test requires Firebase Admin credentials via env "
            "(FIREBASE_SERVICE_ACCOUNT or GOOGLE_APPLICATION_CREDENTIALS)"
        ),
    ),
]

@pytest.mark.integration
def test_build_app_initializes_and_registers_tools():
    # Arrange
    from myapp.wiring import build_app

    # Act
    app = build_app()

    # Assert
    assert hasattr(app, "run")

Test Isolation

Each test should be independent and not share state:

def test_user_created_in_one_test_not_visible_in_another(fake_auth, fake_users_repo):
    # Arrange
    svc1 = UserService(fake_auth, fake_users_repo)

    # Act
    result1 = svc1.add_user(email="test1@example.com", password="secret", name="User1")

    # Assert - second test with fresh fixtures should not see this user
    svc2 = UserService(fake_auth, fake_users_repo)
    users = svc2.list_users()
    assert users["count"] == 1  # Only the user from this test

Testing Anti-Patterns to Avoid

Don't Mock What You Don't Own

❌ Bad - Mocking external library:

@patch('firebase_admin.auth.create_user')
def test_add_user(mock_create_user):
    mock_create_user.return_value = Mock(uid="uid-1")
    # ... test code

✅ Good - Use Fake for your interface:

def test_add_user(fake_auth, fake_users_repo):
    svc = UserService(fake_auth, fake_users_repo)
    # ... test code

Don't Test Implementation Details

❌ Bad - Testing internal method calls:

def test_add_user(fake_auth, fake_users_repo):
    svc = UserService(fake_auth, fake_users_repo)
    svc.add_user(email="test@example.com", password="secret", name="User")
    assert fake_auth.created == [{"uid": "uid-1", ...}]  # Implementation detail

✅ Good - Testing observable behavior:

def test_add_user(fake_auth, fake_users_repo):
    svc = UserService(fake_auth, fake_users_repo)
    result = svc.add_user(email="test@example.com", password="secret", name="User")
    assert result["status"] == "ok"
    assert result["user"]["email"] == "test@example.com"

Don't Skip Error Paths

❌ Bad - Only happy path:

def test_add_user_success(fake_auth, fake_users_repo):
    # Only tests success case

✅ Good - Test all scenarios:

def test_add_user_success(fake_auth, fake_users_repo):
    # Happy path

def test_add_user_rollback_on_firestore_failure(fake_auth, fake_users_repo):
    # Error path

def test_add_user_handles_duplicate_email(fake_auth, fake_users_repo):
    # Edge case

Running Tests

# Run all tests
pytest

# Run with coverage
pytest --cov=myapp --cov-report=term-missing

# Run specific test file
pytest tests/test_user_service.py

# Run specific test
pytest tests/test_user_service.py::test_add_user_success

# Run parametrized tests with verbose output
pytest -v tests/test_user_service.py::test_add_user_parametrized

# Skip integration tests
pytest -m "not integration"

# Run only integration tests
pytest -m integration

# Stop on first failure
pytest -x

# Show local variables on failure
pytest -l

# Run tests in parallel (with pytest-xdist)
pytest -n auto

Best Practices Checklist

  • [ ] Use Fake classes instead of unittest.mock
  • [ ] Structure tests with explicit AAA comments
  • [ ] Use fixtures for test setup
  • [ ] Inject dependencies between fixtures
  • [ ] Parametrize tests for multiple scenarios
  • [ ] Test happy paths and error paths
  • [ ] Test edge cases and boundaries
  • [ ] Write contract tests for interfaces
  • [ ] Ensure test isolation
  • [ ] Use descriptive test names
  • [ ] Keep tests focused on one behavior
  • [ ] Avoid testing implementation details
  • [ ] Test at appropriate level (unit vs integration)
  • [ ] Mock external dependencies appropriately
  • [ ] Maintain test coverage

适合场景

01

OpenClaw 用户查找和安装 Skill 时

02

用户想查找某类 Agent Skill 时

03

需要根据任务场景推荐可安装能力包时

04

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

OpenClaw

79.97%
按下载量换算7,024

安全审计

VirusTotal

通过

ClawScan

通过

Static analysis

未展示

权限和风险

external-service

该 Skill 可能调用第三方服务、云服务或外部模型 API,使用前需要确认账号、额度、数据发送范围和服务条款。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills