F#에서 xUnit 매개변수화 테스트 구현하기

InlineData를 활용한 기본 예제

가장 간단한 형태의 매개변수화 테스트는 정수형 변수를 사용하며, 데이터는 리스트 형태로 제공됩니다:

[<Theory>]
[<InlineData(0)>]
[<InlineData(1)>]
member _.``생산 테스트 확장``(position:int) =
    let testData = [
        {production= ["";"s"];dot= 0;backwards= [];forwards= ["s"];dotmax= false;isKernel= true};
        {production= ["";"s"];dot= 1;backwards= ["s"];forwards= [];dotmax= true;isKernel= true}]
    let item = testData.[position]
    let result = {production= item.production;dot= item.dot}
    Should.equal result.backwards item.backwards
    Should.equal result.forwards item.forwards
    Should.equal result.dotmax item.dotmax
    Should.equal result.isKernel item.isKernel

다중 매개변수 처리

open Xunit
open Xunit.Abstractions

type ParameterTests(output:ITestOutputHelper) =
    [<Theory>]
    [<InlineData(1, 42, 43)>]
    [<InlineData(1,  2,  3)>]
    member _.``덧셈 검증 테스트``(first: int, second: int, expected: int) =
        let sum = first + second
        Assert.Equal(expected, sum)

테스트 메서드의 매개변수 개수는 InlineData 속성의 인자 수와 일치해야 하며, 그렇지 않으면 테스트 탐색기에서 일부 테스트 클래스가 누락될 수 있습니다.

InlineData의 제약사항은 리터럴 상수만 사용할 수 있다는 점입니다. 이는 속성 매개변수가 컴파일 시간에 상수로 결정되어야 하기 때문입니다.

ClassData 방식

ClassDataFSharp.xUnit 네임스페이스의 ClassDataBase를 상속받아 사용합니다:

open FSharp.xUnit

type TestDataSet() = 
    inherit ClassDataBase([ 
        [| 3; 4 |]; 
        [| 32; 42 |] 
    ])

type ClassDataExample(output:ITestOutputHelper) =
    [<Theory>]
    [<ClassData(typeof<TestDataSet>)>]
    member _.comparisonTest (val1 : int, val2 : int) = 
        Assert.NotEqual(val1, val2)

MemberData 활용

xUnit에서 가장 일반적인 접근법은 MemberData를 직접 사용하는 것입니다. 서로 다른 타입의 매개변수를 전달하려면 튜플을 사용해야 하며, FSharp.Reflection 네임스페이스를 효과적으로 활용할 수 있습니다.

동일 타입 내 멤버 참조 시 멤버 이름을 지정합니다:

type DataProviderExample(output:ITestOutputHelper) =
    static member testData =
        [
            [|"John";""|],"John"
            [|"Jane";""|],"Jane"
        ]
        |> Seq.map FSharpValue.GetTupleFields

    [<Theory>]
    [<MemberData(nameof(DataProviderExample.testData))>]
    member _.``문자열 배열 테스트``(input:string[], expected) =
        let actual = input.[0]
        Assert.Equal(actual, expected)

다른 타입의 데이터 참조 시 MemberType 매개변수를 사용합니다:

open FSharp.Reflection

type ExternalData() =
  static member SampleData = 
      [
          "최소 소수는?", 2, true
          "인간이 걸어야 할 길은?", 41, false
      ]
      |> Seq.map FSharpValue.GetTupleFields

type MemberDataUsage(output:ITestOutputHelper) =
    [<Theory; MemberData("SampleData", MemberType=typeof<ExternalData>)>]
    member _.questionTest(question, answer, expected) =
        let validate (q:string) ans =
            q.Split(" ").Length = ans
        Assert.Equal(validate question answer, expected)

고급 활용 패턴

복잡한 데이터 타입을 처리하기 위해 사전을 활용하여 xUnit이 지원하지 않는 타입을 우회할 수 있습니다. 사전의 키를 인라인 데이터 매개변수로 사용하고, 테스트 함수에서 필요한 추가 데이터는 키를 통해 조회합니다:

type ElementUnificationTest(output:ITestOutputHelper) =

    static let testData = [
            "<br/>",[{index= 0;length= 5;value= TagSelfClosing("br",[])}]
            "<p><br></br></p>",[{index= 0;length= 3;value= TagStart("p",[])};{index= 3;length= 4;value= TagSelfClosing("br",[])};{index= 12;length= 4;value= TagEnd "p"}]
        ]

    static let dataMap = Map.ofList testData

    static member keySet = 
        testData
        |> Seq.map (fst>>Array.singleton)
        
    [<Theory;MemberData(nameof ElementUnificationTest.keySet)>]
    member _.``자체 종료 태그 처리``(input:string) =
        let result = 
            input
            |> SeniorTokenizer.tokenize
            |> Seq.choose (HtmlTokenSeniorUtils.unifyVoidElement)
            |> Seq.toList

        let expected = dataMap.[input]
        Should.equal expected result

SingleDataSource를 이용한 최적화

NuGet 패키지 설치:

Install-Package FSharp.xUnit

SingleDataSource 클래스를 활용한 구현 예제:

namespace FSharp.xUnit

open Xunit
open Xunit.Abstractions
open FSharp.xUnit

type DataSourceExample(output:ITestOutputHelper) =
    static let dataSource = SingleDataSource[
        0,[]
        1,[()]
        2,[();()]
    ]
    
    static member keyValues = dataSource.keys

    [<Theory>]
    [<MemberData(nameof DataSourceExample.keyValues)>]
    member _.``단위 리스트 테스트`` (key:int) =
        let generated = List.replicate key ()
        let expected = dataSource.[key]
        Should.equal expected generated

외부 데이터 소스 활용

데이터 소스가 테스트 클래스 외부에 위치할 경우 MemberType 매개변수를 지정해야 합니다:

type ExternalSource() =
    static let source = SingleDataSource[
        "",{index= 0;length= 15;value= DOCTYPE "HTML"}
    ]
    static member keys = source.keys
    static member getItem key = source.[key]

type ConsumerTest(output:ITestOutputHelper) =
    [<Theory>]
    [<MemberData(nameof ExternalSource.keys, MemberType=typeof<ExternalSource>)>]
    member _.``데이터 소스 테스트``(input:string) =
        let processed = Consumption.DS 0 input
        output.WriteLine(stringify processed)
        let expected = ExternalSource.getItem input
        Should.equal processed expected

태그: F# xUnit unit-testing test-parameterization FSharp.xUnit

6월 27일 20:21에 게시됨