Communitygithub.com

fsharp-testing

Modèles de test F# avec xUnit, FsUnit, Unquote, tests basés sur les propriétés FsCheck, tests d'intégration et bonnes pratiques d'organisation des tests. À utiliser lors de l'écriture de tests F# avec xUnit, FsUnit, Unquote ou FsCheck.

Qu'est-ce que fsharp-testing ?

Cette compétence fournit un ensemble complet de modèles de test pour les applications F# utilisant xUnit, FsUnit, Unquote, FsCheck et les pratiques de test modernes .NET. Elle couvre l'écriture de tests unitaires avec xUnit et FsUnit, les assertions enrichies avec Unquote (qui utilise les citations F# pour des messages d'échec clairs), les tests asynchrones, les tests paramétrés avec Theory, et les tests basés sur les propriétés avec FsCheck incluant des générateurs personnalisés. La compétence aborde également le mock de dépendances (avec des stubs de fonctions et NSubstitute pour les interfaces .NET), les tests d'intégration ASP.NET Core utilisant WebApplicationFactory, ainsi que l'organisation des tests dans une structure de projet claire. Elle liste les anti-modèles courants et leurs corrections, et inclut des commandes CLI pour exécuter les tests, mesurer la couverture, filtrer par nom ou activer le mode watch. C'est une ressource essentielle pour tout développeur F# souhaitant mettre en place des tests robustes et maintenables.

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

Demander à votre IA préférée

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

Documentation

Modèles de Test F#

Modèles de test complets pour les applications F# utilisant xUnit, FsUnit, Unquote, FsCheck et les pratiques de test modernes .NET.

Quand Activer

  • Écrire de nouveaux tests pour du code F#
  • Réviser la qualité et la couverture des tests
  • Mettre en place l'infrastructure de test pour des projets F#
  • Déboguer des tests instables ou lents

Pile du Framework de Test

OutilObjectif
xUnitFramework de test (choix standard de l'écosystème .NET)
FsUnit.xUnitSyntaxe d'assertion adaptée à F# pour xUnit
UnquoteBibliothèque d'assertions utilisant les citations F# pour des messages d'échec clairs
FsCheck.xUnitTests basés sur les propriétés intégrés à xUnit
NSubstituteMock des dépendances .NET
TestcontainersInfrastructure réelle dans les tests d'intégration
WebApplicationFactoryTests d'intégration ASP.NET Core

Tests Unitaires avec xUnit + FsUnit

Structure de Test Basique

module OrderServiceTests

open Xunit
open FsUnit.Xunit

[<Fact>]
let ``create définit le statut sur EnAttente`` () =
    let order = Order.create "client-1" [ articleValide ]
    order.Status |> should equal EnAttente

[<Fact>]
let ``confirm change le statut sur Confirmé`` () =
    let order = Order.create "client-1" [ articleValide ]
    let confirmed = Order.confirm order
    confirmed.Status |> should be (ofCase <@ Confirmé @>)

Assertions avec Unquote

Unquote utilise les citations F# pour que les messages d'échec affichent l'expression complète qui a échoué, et pas seulement "attendu X reçu Y".

module OrderValidationTests

open Xunit
open Swensen.Unquote

[<Fact>]
let ``PlaceOrder retourne un succès quand la requête est valide`` () =
    let request = { CustomerId = "client-123"; Items = [ articleValide ] }
    let result = OrderService.placeOrder request
    test <@ Result.isOk result @>

[<Fact>]
let ``le total de la commande additionne les prix des articles`` () =
    let items = [ { Sku = "A"; Quantité = 2; Prix = 10m }
                  { Sku = "B"; Quantité = 1; Prix = 5m } ]
    let total = Order.calculateTotal items
    test <@ total = 25m @>

[<Fact>]
let ``l'email validé rejette une entrée vide`` () =
    let result = ValidatedEmail.create ""
    test <@ Result.isError result @>

Tests Asynchrones

[<Fact>]
let ``PlaceOrder retourne un succès quand la requête est valide`` () = task {
    let deps = createTestDeps ()
    let request = { CustomerId = "client-123"; Items = [ articleValide ] }

    let! result = OrderService.placeOrder deps request

    test <@ Result.isOk result @>
}

[<Fact>]
let ``PlaceOrder retourne une erreur quand les articles sont vides`` () = task {
    let deps = createTestDeps ()
    let request = { CustomerId = "client-123"; Items = [] }

    let! result = OrderService.placeOrder deps request

    test <@ Result.isError result @>
}

Tests Paramétrés avec Theory

[<Theory>]
[<InlineData("")>]
[<InlineData("   ")>]
let ``PlaceOrder rejette un ID client vide`` (customerId: string) =
    let request = { CustomerId = customerId; Items = [ articleValide ] }
    let result = OrderService.placeOrder request
    result |> should be (ofCase <@ Erreur @>)

[<Theory>]
[<InlineData("", false)>]
[<InlineData("a", false)>]
[<InlineData("[email protected]", true)>]
[<InlineData("[email protected]", true)>]
let ``IsValidEmail retourne le résultat attendu`` (email: string, expected: bool) =
    test <@ EmailValidator.isValid email = expected @>

Tests Basés sur les Propriétés avec FsCheck

Utilisation de FsCheck.xUnit

open FsCheck
open FsCheck.Xunit

[<Property>]
let ``le total de la commande est toujours non négatif`` (items: NonEmptyList<PositiveInt * decimal>) =
    let orderItems =
        items.Get
        |> List.map (fun (qty, price) ->
            { Sku = "SKU"; Quantity = qty.Get; Price = abs price })
    let total = Order.calculateTotal orderItems
    total >= 0m

[<Property>]
let ``la sérialisation fait un aller-retour`` (order: Order) =
    let json = JsonSerializer.Serialize order
    let deserialized = JsonSerializer.Deserialize<Order> json
    deserialized = order

Générateurs Personnalisés

type OrderGenerators =
    static member ValidEmail () =
        gen {
            let! user = Gen.elements [ "alice"; "bob"; "carol" ]
            let! domain = Gen.elements [ "exemple.com"; "test.org" ]
            return $"{user}@{domain}"
        }
        |> Arb.fromGen

[<Property(Arbitrary = [| typeof<OrderGenerators> |])>]
let ``les emails valides passent la validation`` (email: string) =
    EmailValidator.isValid email

Mock des Dépendances

Stubs de Fonctions (Préféré)

let createTestDeps () =
    let mutable savedOrders = []
    { FindOrder = fun id -> task { return Map.tryFind id testData }
      SaveOrder = fun order -> task { savedOrders <- order :: savedOrders }
      SendNotification = fun _ -> Task.CompletedTask }

[<Fact>]
let ``PlaceOrder enregistre la commande confirmée`` () = task {
    let mutable saved = []
    let deps =
        { createTestDeps () with
            SaveOrder = fun order -> task { saved <- order :: saved } }

    let! _ = OrderService.placeOrder deps requêteValide

    test <@ saved.Length = 1 @>
}

NSubstitute pour les Interfaces .NET

open NSubstitute

[<Fact>]
let ``appelle le dépôt avec le bon ID`` () = task {
    let repo = Substitute.For<IOrderRepository>()
    repo.FindByIdAsync(Arg.Any<Guid>(), Arg.Any<CancellationToken>())
        .Returns(Task.FromResult(Some testOrder))

    let service = OrderService(repo)
    let! _ = service.GetOrder(testOrder.Id, CancellationToken.None)

    do! repo.Received(1).FindByIdAsync(testOrder.Id, Arg.Any<CancellationToken>())
}

Tests d'Intégration ASP.NET Core

type OrderApiTests (factory: WebApplicationFactory<Program>) =
    interface IClassFixture<WebApplicationFactory<Program>>

    let client =
        factory.WithWebHostBuilder(fun builder ->
            builder.ConfigureServices(fun services ->
                services.RemoveAll<DbContextOptions<AppDbContext>>() |> ignore
                services.AddDbContext<AppDbContext>(fun options ->
                    options.UseInMemoryDatabase("TestDb") |> ignore) |> ignore))
            .CreateClient()

    [<Fact>]
    member _.``GET order retourne 404 quand non trouvé`` () = task {
        let! response = client.GetAsync($"/api/orders/{Guid.NewGuid()}")
        test <@ response.StatusCode = HttpStatusCode.NotFound @>
    }

Organisation des Tests

tests/
  MyApp.Tests/
    Unit/
      OrderServiceTests.fs
      PaymentServiceTests.fs
    Integration/
      OrderApiTests.fs
      OrderRepositoryTests.fs
    Properties/
      OrderPropertyTests.fs
    Helpers/
      TestData.fs
      TestDeps.fs

Anti-Modèles Courants

Anti-ModèleCorrection
Tester les détails d'implémentationTester le comportement et les résultats
État de test partagé mutableÉtat frais par test
Thread.Sleep dans les tests asynchronesUtiliser Task.Delay avec délai d'attente, ou des helpers de sondage
Affirmer sur la sortie de sprintfAffirmer sur des valeurs typées et des filtres par motif
Ignorer CancellationTokenToujours passer et vérifier l'annulation
Ignorer les tests basés sur les propriétésUtiliser FsCheck pour toute fonction avec des invariants clairs

Compétences Associées

  • dotnet-patterns - Modèles .NET idiomatiques, injection de dépendances et architecture
  • csharp-testing - Modèles de test C# (l'infrastructure partagée comme WebApplicationFactory et Testcontainers s'applique aussi à F#)

Exécution des Tests

# Lancer tous les tests
dotnet test

# Lancer avec couverture
dotnet test --collect:"XPlat Code Coverage"

# Lancer un projet spécifique
dotnet test tests/MyApp.Tests/

# Filtrer par nom de test
dotnet test --filter "FullyQualifiedName~OrderService"

# Mode watch pendant le développement
dotnet watch test --project tests/MyApp.Tests/

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