Normes de test pour services Meridian

VérifiéSûr

Définit les normes de test incluant l'évitement de time.Sleep, l'utilisation de testcontainers et les tests table-driven avec justification.

Spar Skills Guide Bot
TestingIntermédiaire
23/07/2026
Claude CodeCursorCopilotCodex
#go#testcontainers#flaky-tests#table-driven-tests#integration-testing

Recommandé pour

Synthèse automatique

Définit les normes de test des services Meridian en Go : suppression des time.Sleep au profit du package await, tests d'intégration PostgreSQL via testcontainers et tests table-driven documentés.

Points forts

  • Remplace time.Sleep par des assertions asynchrones avec timeout et intervalle de polling configurables, ce qui élimine une cause fréquente de tests instables
  • Impose une isolation par conteneur via testdb/testhelpers avec un defer Cleanup systématique
  • Exige un champ rationale par cas de test, ce qui documente l'intention et facilite la revue
  • Rappelle la couverture des chemins d'erreur (defensive testing, ADR-0008) en plus des happy paths

Limites

  • Fortement lié à l'écosystème Meridian (shared/platform/await, testdb, ADR-0008) et peu transposable tel quel ailleurs
  • Suppose Go et testify ; aucun exemple pour d'autres langages ou frameworks de test
  • Le package await est présenté sans code source ni garanties sur sa stratégie de polling en cas d'échec
Quand l'utiliser

À utiliser lorsqu'on écrit ou corrige des tests Go dans un service Meridian, notamment en cas de flakiness, de tests asynchrones ou de tests d'intégration base de données.

Quand l'éviter

À éviter sur un projet non-Meridian ou non-Go, car les packages et conventions référencés n'y existent pas.

Analyse de sécurité

Sûr
Score qualité87/100

The skill contains only static testing guidance and code examples. It declares no tools and does not instruct any destructive, exfiltrating, or obfuscated actions. The content is purely informational for developers.

Aucun point d'attention détecté

Comment cette analyse est faite

Exemples

Fix a flaky async test
My TestOrderCompletion in the payments service uses time.Sleep(2 * time.Second) and fails intermittently in CI. Rewrite it using the Meridian await package with a proper timeout.
Add a PostgreSQL integration test
Write a repository integration test for the ledger service that spins up an isolated Postgres container with testhelpers.SetupTestContainer and verifies Create and FindByID.
Convert to table-driven with rationale
Convert TestDeposit into a table-driven test following Meridian standards, including a rationale field and both happy-path and unhappy-path cases.

name: skill-testing-standards description: Testing standards and best practices for Meridian services triggers:

  • Writing tests
  • Test flakiness or failures
  • Using time.Sleep in tests
  • Async or polling tests
  • Integration tests
  • Database tests
  • Test coverage

instructions: | Use await package instead of time.Sleep. Use testcontainers for database tests. Apply defensive testing (ADR-0008): test happy paths AND unhappy paths. Table-driven tests with rationale field. Use testify assert/require.

Testing Standards

This document defines testing standards for Meridian services.

Core Principles

  1. No time.Sleep - Use the await package for async assertions
  2. Defensive testing - Test happy paths AND unhappy paths (see ADR-0008)
  3. Table-driven tests - Use Go table-driven patterns with rationale
  4. Isolation - Tests must not depend on each other
  5. Fast feedback - Unit tests should complete in milliseconds

Async Testing: Use await Not time.Sleep

NEVER use time.Sleep in tests. It creates flaky tests - too short causes failures, too long wastes CI time.

Use shared/platform/await instead. It polls until conditions are met or timeout.

import "github.com/meridianhub/meridian/shared/platform/await"

// BAD - arbitrary sleep, flaky and slow
time.Sleep(2 * time.Second)
assert.Equal(t, "COMPLETED", order.Status)

// GOOD - polls until condition met or timeout
err := await.Until(func() bool {
    return order.Status == "COMPLETED"
})
require.NoError(t, err)

// With custom timeout and poll interval
err := await.New().
    AtMost(5 * time.Second).
    PollInterval(50 * time.Millisecond).
    Until(func() bool {
        return repo.FindByID(ctx, id) != nil
    })

// Wait for an operation to succeed (no error)
err := await.UntilNoError(func() error {
    return client.HealthCheck()
})

// With context cancellation
err := await.New().
    WithContext(ctx).
    AtMost(10 * time.Second).
    Until(condition)

Defaults: 10s timeout, 100ms poll interval.

For advanced matchers and more expressive assertions, consider gomega.Eventually().

Database Integration Tests: Use Testcontainers

Use shared/platform/testdb/ for PostgreSQL integration tests. Each test gets an isolated container.

import "github.com/meridianhub/meridian/internal/<service>/repository/testhelpers"

func TestRepository(t *testing.T) {
    tc := testhelpers.SetupTestContainer(t)
    defer tc.Cleanup(t)  // CRITICAL - always defer cleanup

    // Use tc.Repo for repository operations
    err := tc.Repo.Create(ctx, entity)
    require.NoError(t, err)

    // Or use tc.Pool for direct SQL
    var count int
    err = tc.Pool.QueryRow(ctx, "SELECT COUNT(*) FROM table").Scan(&count)
    require.NoError(t, err)
}

See Testcontainers Usage Guide for detailed patterns.

Table-Driven Tests with Rationale

Every test case must include a rationale field explaining WHY it matters:

func TestDeposit(t *testing.T) {
    tests := []struct {
        name      string
        amount    int64
        wantErr   bool
        rationale string  // WHY this test case matters
    }{
        // Happy path
        {
            name:      "valid deposit",
            amount:    100,
            wantErr:   false,
            rationale: "Standard valid deposit should succeed",
        },

        // Edge cases
        {
            name:      "minimum valid amount",
            amount:    1,
            wantErr:   false,
            rationale: "Even 1 cent is a valid deposit",
        },

        // Unhappy paths
        {
            name:      "zero amount rejected",
            amount:    0,
            wantErr:   true,
            rationale: "Zero deposits are meaningless operations",
        },
        {
            name:      "negative amount rejected",
            amount:    -100,
            wantErr:   true,
            rationale: "Negative deposits don't make sense (use withdraw)",
        },

        // Defensive: values that shouldn't happen but might
        {
            name:      "overflow detection",
            amount:    math.MaxInt64,
            wantErr:   true,
            rationale: "Must detect arithmetic overflow to prevent corruption",
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := account.Deposit(tt.amount)
            if tt.wantErr {
                assert.Error(t, err, tt.rationale)
            } else {
                assert.NoError(t, err, tt.rationale)
            }
        })
    }
}

Defensive Testing Categories

Apply these categories to all tests (see ADR-0008):

For Numeric Inputs

  • Valid values (happy path)
  • Zero
  • Negative values
  • Maximum/minimum boundaries
  • Overflow conditions

For String Inputs

  • Valid formatted strings
  • Empty strings
  • Whitespace-only strings
  • Invalid formats
  • Very long strings

For State Transitions

  • Valid transitions
  • Invalid transitions (must be rejected)
  • Idempotent operations
  • Concurrent state changes

For Error Handling

  • Every error path has a test
  • State is not modified on error
  • Resources are cleaned up on error
  • Errors wrap correctly (errors.Is/errors.As work)

Test Assertions: Use testify

Use github.com/stretchr/testify for assertions:

import (
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

// Use require for setup - fails fast
tc := testhelpers.SetupTestContainer(t)
require.NotNil(t, tc.Pool, "database pool required")

// Use assert for verifications - continues on failure
assert.Equal(t, expected, actual)
assert.NoError(t, err)
assert.ErrorIs(t, err, ErrNotFound)
assert.Contains(t, logs, "expected message")

require vs assert:

  • require - Stops test immediately on failure (use for setup/preconditions)
  • assert - Records failure but continues (use for verifications)

Test Naming

Use descriptive names that explain the scenario:

// GOOD - describes scenario and expected outcome
func TestDeposit_ZeroAmount_ReturnsError(t *testing.T)
func TestFindByID_NonExistent_ReturnsNotFoundError(t *testing.T)
func TestTransfer_InsufficientFunds_RollsBackTransaction(t *testing.T)

// BAD - vague names
func TestDeposit1(t *testing.T)
func TestError(t *testing.T)
func TestIt(t *testing.T)

Parallel Tests

Run independent tests in parallel for faster feedback:

func TestConcurrentOperations(t *testing.T) {
    t.Parallel()  // Mark test as safe for parallel execution

    tc := testhelpers.SetupTestContainer(t)
    defer tc.Cleanup(t)

    // Each parallel test gets its own container
}

When NOT to parallelize:

  • Tests sharing global state
  • Tests with fixed port bindings
  • Tests modifying environment variables

Mocking Guidelines

Minimize mocking. Prefer real implementations where practical:

  1. Use testcontainers for database tests (real PostgreSQL)
  2. Use in-memory implementations for simple interfaces
  3. Mock only external services that can't be containerized
  4. Never mock the thing you're testing

When mocking is necessary, use github.com/stretchr/testify/mock:

type MockClient struct {
    mock.Mock
}

func (m *MockClient) Send(ctx context.Context, msg Message) error {
    args := m.Called(ctx, msg)
    return args.Error(0)
}

// In test
client := new(MockClient)
client.On("Send", mock.Anything, expectedMsg).Return(nil)

Test Organization

service/
├── domain/
│   ├── account.go
│   └── account_test.go          # Unit tests (same package)
├── repository/
│   ├── postgres_repository.go
│   ├── postgres_repository_test.go      # Integration tests
│   └── testhelpers/
│       └── container.go         # Shared test setup
└── handler/
    ├── grpc_handler.go
    └── grpc_handler_test.go     # Handler tests

CI Considerations

  • Unit tests run on every commit
  • Integration tests (testcontainers) run on PR
  • Tests must pass before merge
  • Flaky tests must be fixed immediately (not skipped)

Related Documentation

Du fichier au premier essai

Le téléchargement contient exactement le contenu affiché sur cette fiche. Cette copie peut différer de la dernière version du dépôt source.

Consulter la source et ses fichiers annexes

Ce téléchargement contient uniquement SKILL.md. Les scripts, références, modèles et dépendances éventuels ne sont pas inclus. Récupérez les fichiers nécessaires depuis la source en conservant leur arborescence avant de poursuivre.

1. Télécharger le fichier

Télécharger SKILL.md

Depuis la racine d’un projet de test, enregistrez le fichier sous .skill-downloads/testing-standards-for-meridian-services-mrx3xztb/SKILL.md, ou utilisez la commande suivante. Ce dossier sert à examiner la skill avant son installation.

Commande pour macOS/Linux ou WSL : un shell POSIX et curl sont nécessaires. Sous Windows sans WSL, utilisez le bouton de téléchargement et créez le dossier manuellement.

mkdir -p '.skill-downloads/testing-standards-for-meridian-services-mrx3xztb' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/testing-standards-for-meridian-services-mrx3xztb/download' --output '.skill-downloads/testing-standards-for-meridian-services-mrx3xztb/SKILL.md.part' && mv '.skill-downloads/testing-standards-for-meridian-services-mrx3xztb/SKILL.md.part' '.skill-downloads/testing-standards-for-meridian-services-mrx3xztb/SKILL.md'

2. Vérifier les prérequis

Lisez SKILL.md avant de l’utiliser. Si le fichier cite des scripts ou services externes, examinez-les et préparez les accès nécessaires dans votre environnement de test.

Aucune section de prérequis reconnue dans ce fichier. Cela ne signifie pas qu’il est autonome : les bibliothèques, outils et accès peuvent être cités ailleurs dans le contenu ou dans le dépôt source.

Les plateformes de la fiche sont des indications du catalogue. Les chemins ci-dessous suivent la documentation des outils ; ils ne prouvent pas que cette skill y a été exécutée. Aucune version minimale de ces outils n’est attestée pour cette fiche.

3. Charger la skill

Après vérification, copiez le dossier complet dans le projet avec l’une des commandes ci-dessous, puis ouvrez une nouvelle session de l’outil. La commande s’arrête si un dossier du même nom existe déjà : comparez les fichiers avant de le remplacer.

Claude Code
test -f '.skill-downloads/testing-standards-for-meridian-services-mrx3xztb/SKILL.md' && test -s '.skill-downloads/testing-standards-for-meridian-services-mrx3xztb/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/skill-testing-standards' && cp -R '.skill-downloads/testing-standards-for-meridian-services-mrx3xztb' '.claude/skills/skill-testing-standards'

Cursor
test -f '.skill-downloads/testing-standards-for-meridian-services-mrx3xztb/SKILL.md' && test -s '.skill-downloads/testing-standards-for-meridian-services-mrx3xztb/SKILL.md' && mkdir -p '.cursor/skills' && test ! -e '.cursor/skills/skill-testing-standards' && cp -R '.skill-downloads/testing-standards-for-meridian-services-mrx3xztb' '.cursor/skills/skill-testing-standards'

Codex
test -f '.skill-downloads/testing-standards-for-meridian-services-mrx3xztb/SKILL.md' && test -s '.skill-downloads/testing-standards-for-meridian-services-mrx3xztb/SKILL.md' && mkdir -p '.agents/skills' && test ! -e '.agents/skills/skill-testing-standards' && cp -R '.skill-downloads/testing-standards-for-meridian-services-mrx3xztb' '.agents/skills/skill-testing-standards'

OpenSkills

OpenSkills doit déjà être installé. La procédure locale install/read est contrôlée avec OpenSkills 1.5.0, qui exige Node.js ≥ 20.6.0. Aucun slug du catalogue n’est résolu automatiquement.

Installation officielle d’OpenSkills
openskills install './.skill-downloads/testing-standards-for-meridian-services-mrx3xztb'

openskills read 'testing-standards-for-meridian-services-mrx3xztb'

La lecture doit afficher le contenu du fichier installé et son dossier de base. Elle ne lance ni la skill ni ses dépendances.

Pour un agent qui lit AGENTS.md, synchronisez ensuite l’index. Cette commande modifie AGENTS.md : relisez le diff avant de l’utiliser.

openskills sync

Mode manuel ou autre outil : joignez SKILL.md à une conversation, ou ouvrez-le dans un agent capable de lire les fichiers. Ajoutez les ressources annexes nécessaires. Il n’existe pas de dossier universel reconnu par tous les outils.

4. Faire un premier essai

Utilisez une entrée courte et fictive : un endpoint d’API, un petit document ou un brief d’interface. Dans une nouvelle conversation, joignez le fichier ou donnez son chemin à l’agent, puis adaptez cette demande :

Lis .skill-downloads/testing-standards-for-meridian-services-mrx3xztb/SKILL.md et applique ses instructions à cet exemple : [décrire une entrée de test]. Indique les fichiers, dépendances ou accès qui manquent. Produis le résultat demandé par la skill sans inventer de données ni exécuter de script non examiné.

Résultat attendu : une sortie correspondant aux instructions de la skill, ou une liste précise des prérequis manquants. Une simple confirmation de lecture ne valide pas le premier usage. Si une ressource externe est indisponible, ce parcours reste à tester.

Skills similaires