Communitygithub.com

fsharp-testing

xUnit, FsUnit, Unquote, FsCheck 속성 기반 테스트, 통합 테스트 및 테스트 구성 모범 사례를 포함한 F# 테스트 패턴입니다. xUnit, FsUnit, Unquote 또는 FsCheck를 사용하여 F# 테스트를 작성할 때 사용합니다.

fsharp-testing란 무엇인가요?

이 스킬은 F# 애플리케이션을 위한 포괄적인 테스트 패턴을 제공하며, xUnit, FsUnit, Unquote, FsCheck 및 최신 .NET 테스트 방식을 통합합니다. 새로운 F# 테스트를 작성하거나, 테스트 품질과 커버리지를 검토하거나, 테스트 인프라를 설정하거나, 불안정하거나 느린 테스트를 디버깅할 때 활성화됩니다. 프레임워크 스택에는 xUnit(표준 .NET 테스트 프레임워크), F# 친화적 어설션 구문을 위한 FsUnit.xUnit, 명확한 실패 메시지를 위한 F# 인용구 기반의 Unquote, 속성 기반 테스트를 위한 FsCheck.xUnit, 목(mocking)을 위한 NSubstitute, 통합 테스트의 실제 인프라를 위한 Testcontainers, ASP.NET Core 통합 테스트를 위한 WebApplicationFactory 등이 포함됩니다. 예제 코드는 FsUnit 및 Unquote를 사용한 단위 테스트, 비동기 테스트, 이론(Theory)을 사용한 매개변수화된 테스트, FsCheck를 사용한 속성 기반 테스트(사용자 정의 생성기 포함), 함수 스텁과 NSubstitute를 사용한 종속성 목(mocking), WebApplicationFactory를 사용한 ASP.NET Core 통합 테스트, 권장되는 테스트 디렉터리 구조를 다룹니다. 또한 일반적인 안티 패턴(예: 구현 세부 사항 테스트, 변경 가능한 공유 상태)과 이에 대한 수정 방법을 설명하고, 관련 스킬(dotnet-patterns, csharp-testing)을 참조하며, dotnet test 명령어를 위한 실행 명령어를 제공합니다.

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

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

F# 테스트 패턴

xUnit, FsUnit, Unquote, FsCheck 및 최신 .NET 테스트 방식을 사용하는 F# 애플리케이션을 위한 포괄적인 테스트 패턴입니다.

활성화 시기

  • F# 코드에 대한 새 테스트 작성 시
  • 테스트 품질 및 커버리지 검토 시
  • F# 프로젝트의 테스트 인프라 설정 시
  • 불안정하거나 느린 테스트 디버깅 시

테스트 프레임워크 스택

도구목적
xUnit테스트 프레임워크 (표준 .NET 에코시스템 선택)
FsUnit.xUnitxUnit을 위한 F# 친화적 어설션 구문
Unquote명확한 실패 메시지를 위한 F# 인용구 기반 어설션 라이브러리
FsCheck.xUnitxUnit과 통합된 속성 기반 테스트
NSubstitute.NET 종속성 목(mocking)
Testcontainers통합 테스트의 실제 인프라
WebApplicationFactoryASP.NET Core 통합 테스트

xUnit + FsUnit을 사용한 단위 테스트

기본 테스트 구조

module OrderServiceTests

open Xunit
open FsUnit.Xunit

[<Fact>]
let ``create sets status to Pending`` () =
    let order = Order.create "cust-1" [ validItem ]
    order.Status |> should equal Pending

[<Fact>]
let ``confirm changes status to Confirmed`` () =
    let order = Order.create "cust-1" [ validItem ]
    let confirmed = Order.confirm order
    confirmed.Status |> should be (ofCase <@ Confirmed @>)

Unquote를 사용한 어설션

Unquote는 F# 인용구를 사용하여 실패 메시지에 "expected X got Y" 대신 실패한 전체 표현식을 표시합니다.

module OrderValidationTests

open Xunit
open Swensen.Unquote

[<Fact>]
let ``PlaceOrder returns success when request is valid`` () =
    let request = { CustomerId = "cust-123"; Items = [ validItem ] }
    let result = OrderService.placeOrder request
    test <@ Result.isOk result @>

[<Fact>]
let ``order total sums item prices`` () =
    let items = [ { Sku = "A"; Quantity = 2; Price = 10m }
                  { Sku = "B"; Quantity = 1; Price = 5m } ]
    let total = Order.calculateTotal items
    test <@ total = 25m @>

[<Fact>]
let ``validated email rejects empty input`` () =
    let result = ValidatedEmail.create ""
    test <@ Result.isError result @>

비동기 테스트

[<Fact>]
let ``PlaceOrder returns success when request is valid`` () = task {
    let deps = createTestDeps ()
    let request = { CustomerId = "cust-123"; Items = [ validItem ] }

    let! result = OrderService.placeOrder deps request

    test <@ Result.isOk result @>
}

[<Fact>]
let ``PlaceOrder returns error when items are empty`` () = task {
    let deps = createTestDeps ()
    let request = { CustomerId = "cust-123"; Items = [] }

    let! result = OrderService.placeOrder deps request

    test <@ Result.isError result @>
}

Theory를 사용한 매개변수화된 테스트

[<Theory>]
[<InlineData("")>]
[<InlineData("   ")>]
let ``PlaceOrder rejects empty customer ID`` (customerId: string) =
    let request = { CustomerId = customerId; Items = [ validItem ] }
    let result = OrderService.placeOrder request
    result |> should be (ofCase <@ Error @>)

[<Theory>]
[<InlineData("", false)>]
[<InlineData("a", false)>]
[<InlineData("[email protected]", true)>]
[<InlineData("[email protected]", true)>]
let ``IsValidEmail returns expected result`` (email: string, expected: bool) =
    test <@ EmailValidator.isValid email = expected @>

FsCheck를 사용한 속성 기반 테스트

FsCheck.xUnit 사용

open FsCheck
open FsCheck.Xunit

[<Property>]
let ``order total is always non-negative`` (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 ``serialization roundtrips`` (order: Order) =
    let json = JsonSerializer.Serialize order
    let deserialized = JsonSerializer.Deserialize<Order> json
    deserialized = order

사용자 정의 생성기

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

[<Property(Arbitrary = [| typeof<OrderGenerators> |])>]
let ``valid emails pass validation`` (email: string) =
    EmailValidator.isValid email

종속성 목(Mocking)

함수 스텁 (권장)

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 saves the confirmed order`` () = task {
    let mutable saved = []
    let deps =
        { createTestDeps () with
            SaveOrder = fun order -> task { saved <- order :: saved } }

    let! _ = OrderService.placeOrder deps validRequest

    test <@ saved.Length = 1 @>
}

.NET 인터페이스를 위한 NSubstitute

open NSubstitute

[<Fact>]
let ``calls repository with correct 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>())
}

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 returns 404 when not found`` () = task {
        let! response = client.GetAsync($"/api/orders/{Guid.NewGuid()}")
        test <@ response.StatusCode = HttpStatusCode.NotFound @>
    }

테스트 구성

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

일반적인 안티 패턴

안티 패턴해결 방법
구현 세부 사항 테스트동작 및 결과 테스트
변경 가능한 공유 테스트 상태테스트별 신규 상태
비동기 테스트에서 Thread.Sleep 사용Task.Delay와 타임아웃 또는 폴링 헬퍼 사용
sprintf 출력에 대한 어설션형식화된 값과 패턴 매치에 대한 어설션
CancellationToken 무시항상 취소 전달 및 확인
속성 기반 테스트 건너뛰기명확한 불변성이 있는 함수에는 FsCheck 사용

관련 스킬

  • dotnet-patterns - 관용적 .NET 패턴, 의존성 주입 및 아키텍처
  • csharp-testing - C# 테스트 패턴 (WebApplicationFactory 및 Testcontainers와 같은 공유 인프라는 F#에도 적용됨)

테스트 실행

# 모든 테스트 실행
dotnet test

# 커버리지와 함께 실행
dotnet test --collect:"XPlat Code Coverage"

# 특정 프로젝트 실행
dotnet test tests/MyApp.Tests/

# 테스트 이름으로 필터링
dotnet test --filter "FullyQualifiedName~OrderService"

# 개발 중 감시 모드
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.

관련 스킬