Przekazać złożone parametry do [teorii]

Xunit ma fajną funkcję : możesz utworzyć jeden test z atrybutem Theory i umieścić dane w atrybutach InlineData, a xUnit wygeneruje wiele testów i przetestuje je wszystkie.

Chcę mieć coś takiego, ale parametry mojej metody nie są " prostymi danymi "(jak string, int, double), ale lista mojej klasy:

public static void WriteReportsToMemoryStream(
    IEnumerable<MyCustomClass> listReport,
    MemoryStream ms,
    StreamWriter writer) { ... }
Author: Community, 2014-02-28

8 answers

Istnieje wiele xxxxData atrybutów w XUnit. Sprawdź na przykład atrybut PropertyData.

Możesz zaimplementować właściwość, która zwraca IEnumerable<object[]>. Każda object[] wygenerowana przez tę metodę zostanie następnie "rozpakowana" jako parametry dla pojedynczego wywołania metody [Theory].

Inną opcją jest ClassData, która działa tak samo, ale pozwala łatwo współdzielić "Generatory" między testami w różnych klasach/przestrzeniach nazw, a także oddziela "Generatory danych" od rzeczywistego testu metody.

Patrz np. te przykłady stąd :

PropertyData Przykład

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 }
            };
        }
    }
}

Przykład 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(); }
}
 143
Author: quetzalcoatl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-22 21:24:10

Aby zaktualizować odpowiedź @Quetzalcoatl: atrybut [PropertyData] został zastąpiony przez [MemberData], który przyjmuje jako argument nazwę łańcuchową dowolnej statycznej metody, pola lub właściwości, która zwraca IEnumerable<object[]>. (Uważam, że szczególnie miło jest mieć metodę iteracyjną, która może faktycznie obliczać przypadki testowe pojedynczo, dając je w miarę ich obliczania.)

Każdy element w sekwencji zwracany przez enumerator jest {[3] } i każda tablica musi być tej samej długości i ta długość musi być liczba argumentów do twojego przypadku testowego (przypisana atrybutem [MemberData] i każdy element musi mieć ten sam typ co odpowiedni parametr metody. (A może mogą to być kabriolety, Nie wiem.)

(Zobacz uwagi do wydania xUnit.net Marzec 2014 i aktualna łatka z przykładowym kodem .)

 42
Author: davidbak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-08 13:33:35

Załóżmy, że mamy klasę samochodów złożonych, która ma klasę producenta:

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; }
}
Wypełnimy i zdamy klasę samochodu na test teoretyczny.

Więc utwórz klasę 'CarClassData', która zwróci instancję klasy Car jak poniżej:

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();
    }

Nadszedł czas na stworzenie metody testowej(CarTest) i zdefiniowanie samochodu jako parametru:

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

typ złożony w teorii

Powodzenia

 17
Author: Iman Bahrampour,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Tworzenie anonimowych tablic obiektowych nie jest najłatwiejszym sposobem konstruowania danych, więc użyłem tego wzorca w moim projekcie.

Najpierw zdefiniuj niektóre klasy współdzielone wielokrotnego użytku:

//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, TExpectedOutput> : 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;
    }

}

Teraz Twoje indywidualne dane testowe i członkowskie są łatwiejsze do napisania i czystsze...

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());
        }
    }
}

Właściwość string Description polega na rzuceniu sobie Kości, gdy jeden z Twoich wielu przypadków testowych nie powiedzie się.

 11
Author: fiat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-22 21:11:05

Możesz spróbować w ten sposób:

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);
    }   
}

Utwórz inną klasę do przechowywania danych testowych:

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;
      }
   }
}
 3
Author: Sandy_Vu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-27 19:57:42

Dla moich potrzeb chciałem po prostu uruchomić serię 'test userów' przez kilka testów-ale [ClassData] itp. wydawało mi się, że to przesada (bo lista elementów była zlokalizowana przy każdym teście).

Więc zrobiłem co następuje, z tablicą wewnątrz testu - indeksowaną z zewnątrz:

[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);
}

To osiągnęło mój cel, utrzymując intencje testu jasno. Musisz tylko synchronizować indeksy, ale to wszystko.

Ładnie wygląda w wynikach, jest do przewijania, a Ty można ponownie uruchomić konkretną instancję, Jeśli pojawi się błąd:

Tutaj wpisz opis obrazka

 1
Author: Simon_Weaver,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-12-29 20:44:48

Tak rozwiązałem twój problem, miałem ten sam scenariusz. Tak więc inline z obiektami niestandardowymi i inną liczbą obiektów na każdym uruchomieniu.

    [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);
    }

Więc to jest mój test jednostkowy, zwróć uwagę na parametr params . Pozwala to na wysłanie innej liczby obiektów. A teraz moja DeviceTelemetryTestData klasa:

    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();
    }
Mam nadzieję, że to pomoże !
 1
Author: Max_Thom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-03-06 14:09:47

Chyba się mylisz. Co atrybut xUnit Theory właściwie oznacza: chcesz przetestować tę funkcję wysyłając specjalne / losowe wartości jako parametry, które otrzymuje ta funkcja-under-test. Oznacza to, że to, co definiujesz jako następny atrybut, np.: InlineData, PropertyData, ClassData, itd.. będzie źródłem tych parametrów. Oznacza to, że należy skonstruować obiekt źródłowy, aby zapewnić te parametry. W Twoim przypadku powinieneś użyć ClassData object jako źródła. Również-należy pamiętać, że ClassData inherits from: IEnumerable<> - oznacza to, że za każdym razem inny zestaw wygenerowanych parametrów będzie używany jako parametry przychodzące dla funkcji-under-test, dopóki IEnumerable<> nie wygeneruje wartości.

Przykład tutaj: Tom DuPont. NET

Przykład może być niepoprawny-długo nie korzystałem z xUnit

 -1
Author: Jasper,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-19 11:31:31