Communitygithub.com

python-testing

Stratégies de test Python avec pytest, méthodologie TDD, fixtures, mocking, paramétrage et exigences de couverture.

Qu'est-ce que python-testing ?

Cette compétence fournit une approche complète pour le test de code Python en utilisant pytest, suivant la méthodologie TDD (Test-Driven Development : cycle rouge, vert, refactor). Elle couvre les concepts fondamentaux comme les fixtures (avec portées et paramètres), le paramétrage avec `@pytest.mark.parametrize`, le mocking avec `unittest.mock`, ainsi que le test du code asynchrone avec `pytest-asyncio`. La compétence guide l'utilisateur dans l'organisation des fichiers de test (conftest, dossiers unit/integration/e2e), la sélection de tests avec des marqueurs personnalisés (comme `@pytest.mark.slow`), et la mesure de la couverture de code (cible de 80%+, chemins critiques 100%). Référencée dans le fichier SKILL.md, elle propose des patrons prêts à l'emploi pour une large gamme de scénarios : assertions, tests d'exceptions, fixtures autouse, mocking de context managers, et utilisation de propriétés. Idéale pour les développeurs Python souhaitant appliquer les meilleures pratiques de test de manière systématique et rigoureuse.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/python-testing

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Patrons de Test Python

Stratégies complètes de test pour applications Python utilisant pytest, la méthodologie TDD et les meilleures pratiques.

Quand Activer

  • Écrire du nouveau code Python (suivre TDD : rouge, vert, refactor)
  • Concevoir des suites de tests pour projets Python
  • Examiner la couverture de test Python
  • Mettre en place l'infrastructure de test

Philosophie Centrale du Test

Développement Piloté par les Tests (TDD)

Suivez toujours le cycle TDD :

  1. ROUGE : Écrire un test échouant pour le comportement souhaité
  2. VERT : Écrire un code minimal pour faire passer le test
  3. REFACTOR : Améliorer le code tout en gardant les tests verts
# Étape 1 : Écrire un test échouant (ROUGE)
def test_add_numbers():
    resultat = addition(2, 3)
    assert resultat == 5

# Étape 2 : Écrire une implémentation minimale (VERT)
def addition(a, b):
    return a + b

# Étape 3 : Refactoriser si nécessaire (REFACTOR)

Exigences de Couverture

  • Cible : 80%+ de couverture de code
  • Chemins critiques : 100% de couverture requis
  • Utilisez pytest --cov pour mesurer la couverture
pytest --cov=mypackage --cov-report=term-missing --cov-report=html

Fondamentaux de pytest

Structure de Base du Test

import pytest

def test_addition():
    """Test de l'addition de base."""
    assert 2 + 2 == 4

def test_majuscule_chaine():
    """Test de la mise en majuscule d'une chaîne."""
    texte = "bonjour"
    assert texte.upper() == "BONJOUR"

def test_ajout_liste():
    """Test d'ajout à une liste."""
    elements = [1, 2, 3]
    elements.append(4)
    assert 4 in elements
    assert len(elements) == 4

Assertions

# Égalité
assert resultat == attendu

# Inégalité
assert resultat != inattendu

# Véracité
assert resultat  # Vrai
assert not resultat  # Faux
assert resultat is True  # Exactement True
assert resultat is False  # Exactement False
assert resultat is None  # Exactement None

# Appartenance
assert element in collection
assert element not in collection

# Comparaisons
assert resultat > 0
assert 0 <= resultat <= 100

# Vérification de type
assert isinstance(resultat, str)

# Test d'exception (approche préférée)
with pytest.raises(ValueError):
    raise ValueError("message d'erreur")

# Vérifier le message d'exception
with pytest.raises(ValueError, match="entrée invalide"):
    raise ValueError("entrée invalide fournie")

# Vérifier les attributs de l'exception
with pytest.raises(ValueError) as exc_info:
    raise ValueError("message d'erreur")
assert str(exc_info.value) == "message d'erreur"

Fixtures

Utilisation de Base des Fixtures

import pytest

@pytest.fixture
def donnees_exemple():
    """Fixture fournissant des données d'exemple."""
    return {"nom": "Alice", "age": 30}

def test_donnees_exemple(donnees_exemple):
    """Test utilisant la fixture."""
    assert donnees_exemple["nom"] == "Alice"
    assert donnees_exemple["age"] == 30

Fixture avec Configuration/Nettoyage

@pytest.fixture
def base_donnees():
    """Fixture avec configuration et nettoyage."""
    # Configuration
    db = BaseDonnees(":memory:")
    db.creer_tables()
    db.inserer_donnees_test()

    yield db  # Fournir au test

    # Nettoyage
    db.fermer()

def test_requete_base_donnees(base_donnees):
    """Test des opérations sur la base de données."""
    resultat = base_donnees.requete("SELECT * FROM utilisateurs")
    assert len(resultat) > 0

Portées des Fixtures

# Portée fonction (défaut) - s'exécute pour chaque test
@pytest.fixture
def fichier_temporaire():
    with open("temp.txt", "w") as f:
        yield f
    os.remove("temp.txt")

# Portée module - s'exécute une fois par module
@pytest.fixture(scope="module")
def db_module():
    db = BaseDonnees(":memory:")
    db.creer_tables()
    yield db
    db.fermer()

# Portée session - s'exécute une fois par session de test
@pytest.fixture(scope="session")
def ressource_partagee():
    ressource = RessourceCouteuse()
    yield ressource
    ressource.nettoyer()

Fixture avec Paramètres

@pytest.fixture(params=[1, 2, 3])
def nombre(request):
    """Fixture paramétrée."""
    return request.param

def test_nombres(nombre):
    """Le test s'exécute 3 fois, une fois pour chaque paramètre."""
    assert nombre > 0

Utilisation de Plusieurs Fixtures

@pytest.fixture
def utilisateur():
    return Utilisateur(id=1, nom="Alice")

@pytest.fixture
def administrateur():
    return Utilisateur(id=2, nom="Admin", role="admin")

def test_interaction_utilisateur_admin(utilisateur, administrateur):
    """Test utilisant plusieurs fixtures."""
    assert administrateur.peut_gerer(utilisateur)

Fixtures Autouse

@pytest.fixture(autouse=True)
def reinitialiser_config():
    """S'exécute automatiquement avant chaque test."""
    Config.reinitialiser()
    yield
    Config.nettoyer()

def test_sans_appel_fixture():
    # reinitialiser_config s'exécute automatiquement
    assert Config.obtenir_parametre("debug") is False

Conftest.py pour les Fixtures Partagées

# tests/conftest.py
import pytest

@pytest.fixture
def client():
    """Fixture partagée pour tous les tests."""
    app = creer_app(testing=True)
    with app.test_client() as client:
        yield client

@pytest.fixture
def entetes_auth(client):
    """Générer les en-têtes d'authentification pour les tests API."""
    reponse = client.post("/api/connexion", json={
        "nom_utilisateur": "test",
        "mot_de_passe": "test"
    })
    jeton = reponse.json["jeton"]
    return {"Authorization": f"Bearer {jeton}"}

Paramétrage

Paramétrage de Base

@pytest.mark.parametrize("entree,attendu", [
    ("bonjour", "BONJOUR"),
    ("monde", "MONDE"),
    ("PyThOn", "PYTHON"),
])
def test_majuscule(entree, attendu):
    """Le test s'exécute 3 fois avec différentes entrées."""
    assert entree.upper() == attendu

Paramètres Multiples

@pytest.mark.parametrize("a,b,attendu", [
    (2, 3, 5),
    (0, 0, 0),
    (-1, 1, 0),
    (100, 200, 300),
])
def test_addition(a, b, attendu):
    """Test de l'addition avec plusieurs entrées."""
    assert addition(a, b) == attendu

Paramétrage avec Identifiants

@pytest.mark.parametrize("entree,attendu", [
    ("[email protected]", True),
    ("invalide", False),
    ("@sans-domaine.com", False),
], ids=["email-valide", "manque-arobase", "manque-domaine"])
def test_validation_email(entree, attendu):
    """Test de validation d'email avec des identifiants lisibles."""
    assert est_email_valide(entree) is attendu

Fixtures Paramétrées

@pytest.fixture(params=["sqlite", "postgresql", "mysql"])
def db(request):
    """Test avec plusieurs moteurs de base de données."""
    if request.param == "sqlite":
        return BaseDonnees(":memory:")
    elif request.param == "postgresql":
        return BaseDonnees("postgresql://localhost/test")
    elif request.param == "mysql":
        return BaseDonnees("mysql://localhost/test")

def test_operations_base_donnees(db):
    """Le test s'exécute 3 fois, une fois pour chaque base."""
    resultat = db.requete("SELECT 1")
    assert resultat is not None

Marqueurs et Sélection de Tests

Marqueurs Personnalisés

# Marquer les tests lents
@pytest.mark.slow
def test_operation_lente():
    time.sleep(5)

# Marquer les tests d'intégration
@pytest.mark.integration
def test_integration_api():
    reponse = requests.get("https://api.exemple.com")
    assert reponse.status_code == 200

# Marquer les tests unitaires
@pytest.mark.unit
def test_logique_unitaire():
    assert calculer(2, 3) == 5

Exécuter des Tests Spécifiques

# Exécuter uniquement les tests rapides
pytest -m "not slow"

# Exécuter uniquement les tests d'intégration
pytest -m integration

# Exécuter les tests d'intégration ou lents
pytest -m "integration or slow"

# Exécuter les tests marqués comme unitaires mais pas lents
pytest -m "unit and not slow"

Configurer les Marqueurs dans pytest.ini

[pytest]
markers =
    slow: marque les tests comme lents
    integration: marque les tests comme tests d'intégration
    unit: marque les tests comme tests unitaires
    django: marque les tests comme nécessitant Django

Mocking et Patching

Mocking de Fonctions

from unittest.mock import patch, Mock

@patch("mypackage.appel_api_externe")
def test_avec_mock(appel_api_mock):
    """Test avec une API externe mockée."""
    appel_api_mock.return_value = {"statut": "succès"}

    resultat = ma_fonction()

    appel_api_mock.assert_called_once()
    assert resultat["statut"] == "succès"

Mocking de Valeurs de Retour

@patch("mypackage.BaseDonnees.connecter")
def test_connexion_base_donnees(connecter_mock):
    """Test avec une connexion base de données mockée."""
    connecter_mock.return_value = MockConnexion()

    db = BaseDonnees()
    db.connecter()

    connecter_mock.assert_called_once_with("localhost")

Mocking d'Exceptions

@patch("mypackage.appel_api")
def test_gestion_erreur_api(appel_api_mock):
    """Test de la gestion d'erreur avec exception mockée."""
    appel_api_mock.side_effect = ErreurConnexion("Erreur réseau")

    with pytest.raises(ErreurConnexion):
        appel_api()

    appel_api_mock.assert_called_once()

Mocking de Gestionnaires de Contexte

@patch("builtins.open", new_callable=mock_open)
def test_lecture_fichier(fichier_mock):
    """Test de lecture de fichier avec open mocké."""
    fichier_mock.return_value.read.return_value = "contenu du fichier"

    resultat = lire_fichier("test.txt")

    fichier_mock.assert_called_once_with("test.txt", "r")
    assert resultat == "contenu du fichier"

Utilisation d'Autospec

@patch("mypackage.ConnexionBD", autospec=True)
def test_autospec(db_mock):
    """Test avec autospec pour détecter une mauvaise utilisation de l'API."""
    db = db_mock.return_value
    db.requete("SELECT * FROM utilisateurs")

    # Ceci échouerait si ConnexionBD n'a pas de méthode requete
    db_mock.assert_called_once()

Mocking d'Instances de Classe

class TestServiceUtilisateur:
    @patch("mypackage.DepotUtilisateurs")
    def test_creer_utilisateur(self, depot_mock):
        """Test de création d'utilisateur avec dépôt mocké."""
        depot_mock.return_value.sauvegarder.return_value = Utilisateur(id=1, nom="Alice")

        service = ServiceUtilisateur(depot_mock.return_value)
        utilisateur = service.creer_utilisateur(nom="Alice")

        assert utilisateur.nom == "Alice"
        depot_mock.return_value.sauvegarder.assert_called_once()

Mocking de Propriété

@pytest.fixture
def mock_config():
    """Créer un mock avec une propriété."""
    config = Mock()
    type(config).debug = PropertyMock(return_value=True)
    type(config).cle_api = PropertyMock(return_value="cle-test")
    return config

def test_avec_mock_config(mock_config):
    """Test avec des propriétés de configuration mockées."""
    assert mock_config.debug is True
    assert mock_config.cle_api == "cle-test"

Test de Code Asynchrone

Tests Async avec pytest-asyncio

import pytest

@pytest.mark.asyncio
async def test_fonction_async():
    """Test d'une fonction asynchrone."""
    resultat = await addition_async(2, 3)
    assert resultat == 5

@pytest.mark.asyncio
async def test_async_avec_fixture(client_async):
    """Test asynchrone avec fixture asynchrone."""
    reponse = await client_async.get("/api/utilisateurs")
    assert reponse.status_code == 200

Fixture Asynchrone

@pytest.fixture
async def client_async():
    """Fixture asynchrone fournissant un client de test asynchrone."""
    app = creer_app()
    async with app.test_client() as client:
        yield client

@pytest.mark.asyncio
async def test_point_acces_api(client_async):
    """Test utilisant une fixture asynchrone."""
    reponse = await client_async.get("/api/donnees")
    assert reponse.status_code == 200

Mocking de Fonctions Async

@pytest.mark.asyncio
@patch("mypackage.appel_api_async")
async def test_mock_async(appel_api_mock):
    """Test d'une fonction asynchrone avec mock."""
    appel_api_mock.return_value = {"statut": "ok"}

    resultat = await ma_fonction_async()

    appel_api_mock.assert_awaited_once()
    assert resultat["statut"] == "ok"

Test des Exceptions

Test des Exceptions Attendues

def test_division_par_zero():
    """Test que la division par zéro lève ZeroDivisionError."""
    with pytest.raises(ZeroDivisionError):
        diviser(10, 0)

def test_exception_personnalisee():
    """Test d'une exception personnalisée avec message."""
    with pytest.raises(ValueError, match="entrée invalide"):
        valider_entree("invalide")

Test des Attributs d'Exception

def test_exception_avec_details():
    """Test d'une exception avec attributs personnalisés."""
    with pytest.raises(ErreurPersonnalisee) as exc_info:
        raise ErreurPersonnalisee("erreur", code=400)

    assert exc_info.value.code == 400
    assert "erreur" in str(exc_info.value)

Test des Effets de Bord

Test des Opérations sur Fichiers

import tempfile
import os

def test_traitement_fichier():
    """Test du traitement de fichier avec fichier temporaire."""
    with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
        f.write("contenu de test")
        chemin_temp = f.name

    try:
        resultat = traiter_fichier(chemin_temp)
        assert resultat == "traité: contenu de test"
    finally:
        os.unlink(chemin_temp)

Test avec la Fixture tmp_path de pytest

def test_avec_chemin_temp(tmp_path):
    """Test utilisant la fixture de chemin temporaire intégrée de pytest."""
    fichier_test = tmp_path / "test.txt"
    fichier_test.write_text("bonjour le monde")

    resultat = traiter_fichier(str(fichier_test))
    assert resultat == "bonjour le monde"
    # tmp_path est automatiquement nettoyé

Test avec la Fixture tmpdir

def test_avec_repertoire_temp(tmpdir):
    """Test utilisant la fixture tmpdir de pytest."""
    fichier_test = tmpdir.join("test.txt")
    fichier_test.write("données")

    resultat = traiter_fichier(str(fichier_test))
    assert resultat == "données"

Organisation des Tests

Structure des Répertoires

tests/
├── conftest.py                 # Fixtures partagées
├── __init__.py
├── unit/                       # Tests unitaires
│   ├── __init__.py
│   ├── test_modeles.py
│   ├── test_utilitaires.py
│   └── test_services.py
├── integration/                # Tests d'intégration
│   ├── __init__.py
│   ├── test_api.py
│   └── test_base_donnees.py
└── e2e/                        # Tests de bout en bout
    ├── __init__.py
    └── test_parcours_utilisateur.py

Classes de Test

class TestServiceUtilisateur:
    """Grouper les tests liés dans une classe."""

    @pytest.fixture(autouse=True)
    def mise_en_place(self):
        """Configuration exécutée avant chaque test de cette classe."""
        self.service = ServiceUtilisateur()

    def test_creer_utilisateur(self):
        """Test de création d'utilisateur."""
        utilisateur = self.service.creer_utilisateur("Alice")
        assert utilisateur.nom == "Alice"

    def test_supprimer_utilisateur(self):
        """Test de suppression d'utilisateur."""
        utilisateur = self.service.creer_utilisateur("Bob")
        self.service.supprimer_utilisateur(utilisateur.id)
        assert self.service.obtenir_utilisateur(utilisateur.id) is None

    def test_mettre_a_jour_utilisateur(self):
        """Test de mise à jour d'utilisateur."""
        utilisateur = self.service.creer_utilisateur("Charlie")
        utilisateur_mis_a_jour = self.service.mettre_a_jour_utilisateur(
            utilisateur.id, nom="Charles"
        )
        assert utilisateur_mis_a_jour.nom == "Charles"

Bonnes Pratiques et Conseils

Organisation des Tests

  1. Un test = un comportement : Chaque test doit vérifier une seule chose
  2. Nommer clairement : test_fonctionnalite_testee_condition_attendue
  3. Utiliser l'arrangement, l'action, l'assertion (AAA) : Configurer, exécuter, vérifier
  4. Éviter les tests fragiles : Tester le comportement, pas l'implémentation
  5. Garder les tests indépendants : Un test ne doit pas dépendre d'un autre

Exemple de Structure AAA

def test_calculer_reduction_membre_fidele():
    # Arrangement
    membre = Membre(niveau="fidèle", total_achats=1000)
    montant_achat = 200

    # Action
    reduction = calculer_reduction(membre, montant_achat)

    # Assertion
    assert reduction == 40  # 20% de réduction pour les membres fidèles
    assert reduction <= montant_achat * 0.2

Configuration pytest

[pytest]
# Fichier pytest.ini recommandé
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*

afd_opts = -ra -q --tb=short
markers =
    lent: tests lents
    integration: tests d'intégration
    unite: tests unitaires
    smoke: tests de fumée

# Couverture
addopts =
    --cov=mypackage
    --cov-report=term-missing
    --cov-report=html

Exemple complet de test de service

import pytest
from unittest.mock import patch, Mock

@pytest.mark.unite
class TestServiceCommandes:
    """Tests unitaires pour ServiceCommandes."""

    @pytest.fixture(autouse=True)
    def mise_en_place(self):
        """Configurer les mocks communs."""
        self.depot_commandes = Mock()
        self.service = ServiceCommandes(depot_commandes=self.depot_commandes)

    @pytest.mark.parametrize("statut_commande,prix_attendu", [
        ("en_attente", 100.0),
        ("expédiée", 100.0),
        ("livrée", 100.0),
        ("annulée", 0.0),
    ])
    def test_calculer_prix_selon_statut(self, statut_commande, prix_attendu):
        """Doit retourner le prix correct selon le statut de la commande."""
        commande = Commande(statut=statut_commande, montant=100.0)
        self.depot_commandes.obtenir_par_id.return_value = commande

        prix = self.service.calculer_prix(commande_id=1)

        assert prix == prix_attendu

    @patch("mypackage.service.envoi_email")
    def test_envoyer_confirmation_commande(self, envoi_email_mock):
        """Doit envoyer un email de confirmation après création de commande."""
        commande = self.service.creer_commande(
            utilisateur_id=1,
            articles=[Article(id=1, prix=50.0)]
        )

        envoi_email_mock.assert_called_once_with(
            destinataire="[email protected]",
            sujet="Confirmation de commande",
            corps=f"Votre commande {commande.id} a été créée."
        )

    def test_creer_commande_invalide_articles_vides(self):
        """Doit lever une exception pour une commande sans articles."""
        with pytest.raises(ValueError, match="Au moins un article requis"):
            self.service.creer_commande(
                utilisateur_id=1,
                articles=[]
            )

Ressources Supplémentaires

Pièges Courants à Éviter

  1. Tests dépendants de l'ordre : Ne pas partager l'état entre les tests
  2. Tests trop larges : Tester une seule préoccupation par test
  3. Négliger les cas limites : Tester les cas vides, nuls et limites
  4. Sous-estimer les fixtures : Utiliser conftest.py pour les fixtures partagées
  5. Ignorer les tests async : Toujours utiliser pytest-asyncio pour les fonctions async
  6. Sauts de couverture : Viser 80% et 100% sur les chemins critiques

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

Skills associés