복잡한 매개 변수를 [이론]에 전달


98

Xunit에는 멋진 기능 이 있습니다. Theory속성으로 하나의 테스트를 만들고 속성에 데이터를 넣을 수 InlineData있으며 xUnit은 많은 테스트를 생성하고 모두 테스트합니다.

나는 이런 식으로 뭔가를 갖고 싶어하지만, 내 방법에 매개 변수가없는 '단순 데이터'(등이있다 string, int, double),하지만 내 클래스의 목록 :

public static void WriteReportsToMemoryStream(
    IEnumerable<MyCustomClass> listReport,
    MemoryStream ms,
    StreamWriter writer) { ... }

3
환경에서 의미가 있다면 훨씬 적은 노이즈로 F #에서 수행 할 수 있습니다.- stackoverflow.com
35127997/11635

1
시험 방법에 매개 변수로 복잡한 객체를 전송 완전 가이드 단위 테스트에서 복잡한 유형을
이만 Bahrampour

답변:


137

xxxxDataXUnit 에는 많은 속성 이 있습니다 . 예를 들어 PropertyData속성을 확인하십시오 .

를 반환하는 속성을 구현할 수 있습니다 IEnumerable<object[]>. object[]이 메서드가 생성하는 각 메서드는 메서드에 대한 단일 호출에 대한 매개 변수로 "압축 해제"됩니다 [Theory].

또 다른 옵션은 ClassData동일하게 작동하지만 다른 클래스 / 네임 스페이스의 테스트간에 '생성자'를 쉽게 공유 할 수 있으며 실제 테스트 방법과 '데이터 생성기'를 분리합니다.

여기에서 다음 예를 참조 하십시오 .

PropertyData 예

public class StringTests2
{
    [Theory, PropertyData(nameof(SplitCountData))]
    public void SplitCount(string input, int expectedCount)
    {
        var actualCount = input.Split(' ').Count();
        Assert.Equal(expectedCount, actualCount);
    }

    public static IEnumerable<object[]> SplitCountData
    {
        get
        {
            // Or this could read from a file. :)
            return new[]
            {
                new object[] { "xUnit", 1 },
                new object[] { "is fun", 2 },
                new object[] { "to test with", 3 }
            };
        }
    }
}

ClassData 예

public class StringTests3
{
    [Theory, ClassData(typeof(IndexOfData))]
    public void IndexOf(string input, char letter, int expected)
    {
        var actual = input.IndexOf(letter);
        Assert.Equal(expected, actual);
    }
}

public class IndexOfData : IEnumerable<object[]>
{
    private readonly List<object[]> _data = new List<object[]>
    {
        new object[] { "hello world", 'w', 6 },
        new object[] { "goodnight moon", 'w', -1 }
    };

    public IEnumerator<object[]> GetEnumerator()
    { return _data.GetEnumerator(); }

    IEnumerator IEnumerable.GetEnumerator()
    { return GetEnumerator(); }
}

@dcastro : 네, 사실 원래 xUnit의 워드 프로세서에 대한 몇 가지 찾고 있어요
케찰코아틀의

2
@Nick : PropertyData와 비슷하다는 데 동의하지만 그 이유도 지적했습니다 : static. 그게 바로 내가하지 않는 이유입니다. ClassData는 정적에서 벗어나고 싶을 때입니다. 이렇게하면 생성기를 더 쉽게 재사용 (즉, 중첩) 할 수 있습니다.
케찰코아틀

1
ClassData에서 무슨 일이 일어 났는지 아이디어가 있습니까? xUnit2.0에서 찾을 수 없습니다. 지금은 클래스의 새 인스턴스를 만들고이를 반환하는 정적 메서드와 함께 MemberData를 사용하고 있습니다.
Erti-Chris Eelmaa

14
@Erti, 특성 [MemberData("{static member}", MemberType = typeof(MyClass))]을 바꾸는 데 사용 ClassData합니다.
Junle Li

6
C # 6부터는 nameof속성 이름을 하드 코딩하는 대신 키워드 를 사용하는 것이 좋습니다 (쉽지만 조용히 중단됨).
Sara

40

@Quetzalcoatl의 대답을 업데이트하려면 다음을 수행하십시오. 속성 [PropertyData]은 .NET Framework [MemberData]를 반환하는 정적 메서드, 필드 또는 속성의 문자열 이름을 인수 로 사용 하는 것으로 대체되었습니다 IEnumerable<object[]>. ( 한 번에 하나씩 테스트 사례를 실제로 계산할 수있는 반복기 메서드를 사용 하여 계산 된대로 산출 하는 것이 특히 좋습니다 .)

열거자가 반환하는 시퀀스의 각 요소는 an object[]이고 각 배열은 길이가 동일해야하며 해당 길이는 테스트 케이스에 대한 인수의 수 여야합니다 (속성으로 주석 처리 [MemberData]되고 각 요소는 해당 메서드 매개 변수와 동일한 유형을 가져야합니다). . (아니면 컨버터블 유형일 수도 있지만 모르겠습니다.)

( xUnit.net 2014 년 3 월 릴리스 노트예제 코드가있는 실제 패치를 참조하십시오 .)


2
@davidbak codplex가 사라졌습니다. 링크가 작동하지 않습니다
Kishan Vaishnav을

11

익명 객체 배열을 만드는 것은 데이터를 구성하는 가장 쉬운 방법이 아니므로이 패턴을 프로젝트에서 사용했습니다.

먼저 재사용 가능한 공유 클래스를 정의하십시오.

//http://stackoverflow.com/questions/22093843
public interface ITheoryDatum
{
    object[] ToParameterArray();
}

public abstract class TheoryDatum : ITheoryDatum
{
    public abstract object[] ToParameterArray();

    public static ITheoryDatum Factory<TSystemUnderTest, TExpectedOutput>(TSystemUnderTest sut, TExpectedOutput expectedOutput, string description)
    {
        var datum= new TheoryDatum<TSystemUnderTest, TExpectedOutput>();
        datum.SystemUnderTest = sut;
        datum.Description = description;
        datum.ExpectedOutput = expectedOutput;
        return datum;
    }
}

public class TheoryDatum<TSystemUnderTest, TExecptedOutput> : TheoryDatum
{
    public TSystemUnderTest SystemUnderTest { get; set; }

    public string Description { get; set; }

    public TExpectedOutput ExpectedOutput { get; set; }

    public override object[] ToParameterArray()
    {
        var output = new object[3];
        output[0] = SystemUnderTest;
        output[1] = ExpectedOutput;
        output[2] = Description;
        return output;
    }

}

이제 개별 테스트 및 멤버 데이터를 더 쉽게 작성하고 정리할 수 있습니다.

public class IngredientTests : TestBase
{
    [Theory]
    [MemberData(nameof(IsValidData))]
    public void IsValid(Ingredient ingredient, bool expectedResult, string testDescription)
    {
        Assert.True(ingredient.IsValid == expectedResult, testDescription);
    }

    public static IEnumerable<object[]> IsValidData
    {
        get
        {
            var food = new Food();
            var quantity = new Quantity();
            var data= new List<ITheoryDatum>();

            data.Add(TheoryDatum.Factory(new Ingredient { Food = food }                       , false, "Quantity missing"));
            data.Add(TheoryDatum.Factory(new Ingredient { Quantity = quantity }               , false, "Food missing"));
            data.Add(TheoryDatum.Factory(new Ingredient { Quantity = quantity, Food = food }  , true,  "Valid"));

            return data.ConvertAll(d => d.ToParameterArray());
        }
    }
}

문자열 Description속성은 많은 테스트 사례 중 하나가 실패 할 때 뼈를 던지는 것입니다.


1
나는 이것을 좋아한다; 90 개 이상의 속성에 대한 유효성 검사를 검증해야하는 매우 복잡한 개체에 대한 실제 잠재력이 있습니다. 간단한 JSON 객체를 전달하고 역 직렬화하고 테스트 반복을위한 데이터를 생성 할 수 있습니다. 잘 했어.
Gustyn

1
IsValid Testmethod의 매개 변수가 섞여 있지 않습니까? IsValid (ingrediant, exprectedResult, testDescription)이 아니어야합니까?
pastacool

9

Manufacturer 클래스가있는 복잡한 Car 클래스가 있다고 가정합니다.

public class Car
{
     public int Id { get; set; }
     public long Price { get; set; }
     public Manufacturer Manufacturer { get; set; }
}
public class Manufacturer
{
    public string Name { get; set; }
    public string Country { get; set; }
}

우리는 Car 클래스를 채우고 이론 테스트를 통과 할 것입니다.

따라서 아래와 같이 Car 클래스의 인스턴스를 반환하는 'CarClassData'클래스를 만듭니다.

public class CarClassData : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[] {
                new Car
                {
                  Id=1,
                  Price=36000000,
                  Manufacturer = new Manufacturer
                  {
                    Country="country",
                    Name="name"
                  }
                }
            };
        }
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

테스트 메소드 (CarTest)를 생성하고 자동차를 매개 변수로 정의 할 때입니다.

[Theory]
[ClassData(typeof(CarClassData))]
public void CarTest(Car car)
{
     var output = car;
     var result = _myRepository.BuyCar(car);
}

이론상 복잡한 유형

행운을 빕니다


3
이 답변은 선택한 답변에서 누락 된 것으로 보이는 이론 입력으로 사용자 지정 유형을 전달하는 문제를 명시 적으로 해결합니다.
JD Cain

1
이것은 내가 찾고 있던 사용 사례로, 복잡한 유형을 매개 변수로 이론에 전달하는 방법입니다. 완벽하게 작동합니다! 이것은 MVP 패턴을 테스트하는 데 큰 도움이됩니다. 이제 모든 종류의 상태에서 View의 여러 다른 인스턴스를 설정하고 모두 Presenter 메서드가 해당 뷰에 미치는 영향을 테스트하는 동일한 이론으로 전달할 수 있습니다. 그것을 사랑하십시오!
Denis M. Kitchen

3

이 방법으로 시도 할 수 있습니다.

public class TestClass {

    bool isSaturday(DateTime dt)
    {
       string day = dt.DayOfWeek.ToString();
       return (day == "Saturday");
    }

    [Theory]
    [MemberData("IsSaturdayIndex", MemberType = typeof(TestCase))]
    public void test(int i)
    {
       // parse test case
       var input = TestCase.IsSaturdayTestCase[i];
       DateTime dt = (DateTime)input[0];
       bool expected = (bool)input[1];

       // test
       bool result = isSaturday(dt);
       result.Should().Be(expected);
    }   
}

테스트 데이터를 보관할 다른 클래스를 만듭니다.

public class TestCase
{
   public static readonly List<object[]> IsSaturdayTestCase = new List<object[]>
   {
      new object[]{new DateTime(2016,1,23),true},
      new object[]{new DateTime(2016,1,24),false}
   };

   public static IEnumerable<object[]> IsSaturdayIndex
   {
      get
      {
         List<object[]> tmp = new List<object[]>();
            for (int i = 0; i < IsSaturdayTestCase.Count; i++)
                tmp.Add(new object[] { i });
         return tmp;
      }
   }
}

1

내 필요에 따라 몇 가지 테스트를 통해 일련의 '테스트 사용자'를 실행하고 싶었지만 [ClassData] 등은 내가 필요한 항목에 대해 과도하게 보였습니다 (항목 목록이 각 테스트에 현지화 되었기 때문).

그래서 테스트 내부에 배열을 사용하여 다음을 수행했습니다.

[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public async Task Account_ExistingUser_CorrectPassword(int userIndex)
{
    // DIFFERENT INPUT DATA (static fake users on class)
    var user = new[]
    {
        EXISTING_USER_NO_MAPPING,
        EXISTING_USER_MAPPING_TO_DIFFERENT_EXISTING_USER,
        EXISTING_USER_MAPPING_TO_SAME_USER,
        NEW_USER

    } [userIndex];

    var response = await Analyze(new CreateOrLoginMsgIn
    {
        Username = user.Username,
        Password = user.Password
    });

    // expected result (using ExpectedObjects)
    new CreateOrLoginResult
    {
        AccessGrantedTo = user.Username

    }.ToExpectedObject().ShouldEqual(response);
}

이것은 테스트의 의도를 명확하게 유지하면서 내 목표를 달성했습니다. 인덱스를 동기화 상태로 유지하기 만하면되지만 그게 전부입니다.

결과가보기 좋고 접을 수 있으며 오류가 발생하면 특정 인스턴스를 다시 실행할 수 있습니다.

여기에 이미지 설명 입력


"결과가 멋져 보이며 접을 수 있으며 오류가 발생하면 특정 인스턴스를 다시 실행할 수 있습니다." 아주 좋은 지적입니다. 의 주요 단점은 MemberData특정 테스트 입력으로 테스트를 보거나 실행할 수 없다는 것입니다. 짜증 난다.
Oliver Pearmain

사실, 나는 MemberData당신이 사용 하는 경우 TheoryData와 선택적으로 가능하다는 것을 방금 알아 냈습니다 IXunitSerializable. 더 많은 정보와 예는 여기에 있습니다 ... github.com/xunit/xunit/issues/429#issuecomment-108187109
Oliver Pearmain

1

이것이 내가 당신의 문제를 해결 한 방법이며, 동일한 시나리오를 가졌습니다. 따라서 각 실행에서 사용자 지정 개체 및 다른 수의 개체와 인라인됩니다.

    [Theory]
    [ClassData(typeof(DeviceTelemetryTestData))]
    public async Task ProcessDeviceTelemetries_TypicalDeserialization_NoErrorAsync(params DeviceTelemetry[] expected)
    {
        // Arrange
        var timeStamp = DateTimeOffset.UtcNow;

        mockInflux.Setup(x => x.ExportTelemetryToDb(It.IsAny<List<DeviceTelemetry>>())).ReturnsAsync("Success");

        // Act
        var actual = await MessageProcessingTelemetry.ProcessTelemetry(JsonConvert.SerializeObject(expected), mockInflux.Object);

        // Assert
        mockInflux.Verify(x => x.ExportTelemetryToDb(It.IsAny<List<DeviceTelemetry>>()), Times.Once);
        Assert.Equal("Success", actual);
    }

그래서 이것은 내 단위 테스트 입니다. params 매개 변수에 주목하세요 . 이를 통해 다른 수의 개체를 보낼 수 있습니다. 그리고 이제 내 DeviceTelemetryTestData 클래스 :

    public class DeviceTelemetryTestData : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } };
            yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } };
            yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } };
            yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } };
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

도움이 되길 바랍니다!


-1

여기서 착각하신 것 같아요. xUnit Theory속성의 실제 의미 : 테스트 대상 함수가받는 매개 변수로 특수 / 무작위 값을 전송하여이 함수를 테스트하려고합니다. 당신이 같은 다음의 속성으로 정의 어떤 것을 의미 : InlineData, PropertyData, ClassData, 등이 그 매개 변수에 대한 원천이 될 것입니다. 즉, 해당 매개 변수를 제공하려면 소스 오브젝트를 구성해야합니다. 귀하의 경우에는 ClassData객체를 소스로 사용해야한다고 생각합니다 . 또한-다음에서 ClassData상속 IEnumerable<>됨에 유의하십시오 .-이는 생성 된 매개 변수의 다른 세트 IEnumerable<>가 값을 생성 할 때까지 테스트 대상 함수에 대한 수신 매개 변수로 사용될 때마다 사용됨을 의미 합니다.

예 : Tom DuPont .NET

예가 올바르지 않을 수 있습니다. 오랫동안 xUnit을 사용하지 않았습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.