Karmaşık parametreleri [Teori] 'ye geçirin


100

Xunit'in güzel bir özelliği var : bir Theoryöznitelik ile bir test oluşturabilir ve özniteliklere veri koyabilirsiniz InlineDatave xUnit birçok test oluşturacak ve hepsini test edecektir.

Böyle bir şey olsun istiyorum ama benim yöntemine parametreler değil 'basit veri' (gibidir string, int, double), ama benim sınıfın bir listesi:

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

Yanıtlar:


139

xxxxDataXUnit'te birçok öznitelik vardır. Örneğin PropertyDataözniteliğe göz atın .

Dönen bir özelliği uygulayabilirsiniz IEnumerable<object[]>. object[]Bu yöntemin ürettiği her biri , daha sonra, [Theory]yönteminize yapılan tek bir çağrı için bir parametre olarak "paketten çıkarılır" .

Diğer bir seçenek, ClassDataaynı şekilde çalışan, ancak farklı sınıflarda / ad alanlarındaki testler arasında 'üreteçleri' kolayca paylaşmaya izin veren ve aynı zamanda 'veri oluşturucuları' gerçek test yöntemlerinden ayıran özelliktir.

IE Bkz buradan bu örnekleri :

PropertyData Örneği

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 Örneği

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: evet, aslında orijinal xunit belgelerinde bazılarını arıyorum
quetzalcoatl

2
@ Nick: Bunu PropertyData benzer olduğuna katılıyorum ama aynı zamanda, bunun nedenini dikkat çekmişlerdir: static. İşte bu yüzden yapmam. ClassData, statikten kaçmak istediğiniz zamandır. Bunu yaparak, jeneratörleri daha kolay yeniden kullanabilirsiniz (yani iç içe yerleştirebilirsiniz).
quetzalcoatl

1
ClassData ile ne olduğu hakkında bir fikriniz var mı? Bunu xUnit2.0'da bulamıyorum, şimdilik MemberData'yı yeni sınıf örneği oluşturan ve onu döndüren statik yöntemle kullanıyorum.
Erti-Chris Eelmaa

14
@Erti, niteliği [MemberData("{static member}", MemberType = typeof(MyClass))]değiştirmek için kullanın ClassData.
Junle Li

7
C # 6'dan itibaren, nameofbir özellik adını sabit kodlamak yerine anahtar kelimenin kullanılması önerilir (kolayca ancak sessizce kırılır).
sara

41

@ Quetzalcoatl'ın cevabını güncellemek için: Özniteliğin [PropertyData]yerini [MemberData], herhangi bir statik yöntemin, alanın veya bir IEnumerable<object[]>. ( Test senaryolarını birer birer hesaplayıp , hesaplandıkça ortaya çıkaran bir yineleyici yöntemine sahip olmayı özellikle güzel buluyorum .)

Numaralandırıcı tarafından döndürülen dizideki her öğe bir öğedir object[]ve her dizi aynı uzunlukta olmalıdır ve bu uzunluk, test durumunuza ait argüman sayısı olmalıdır (öznitelikle açıklanmıştır [MemberData]ve her öğe, karşılık gelen yöntem parametresiyle aynı türe sahip olmalıdır. . (Ya da dönüştürülebilir tipler olabilirler, bilmiyorum.)

( Mart 2014 xUnit.net sürüm notlarına ve örnek kodlu gerçek yamaya bakın .)


3
@davidbak Codplex gitti. Bağlantı çalışmıyor
Kishan Vaishnav

10

Bir Manufacturer sınıfına sahip karmaşık bir Araba sınıfımız olduğunu varsayalım:

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

Araba sınıfını doldurup bir Teori testine geçireceğiz.

Dolayısıyla, aşağıdaki gibi Car sınıfının bir örneğini döndüren bir "CarClassData" sınıfı oluşturun:

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

Bir test yöntemi (CarTest) oluşturma ve arabayı bir parametre olarak tanımlama zamanı:

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

teoride karmaşık tip

İyi şanslar


3
Bu cevap, seçilen cevapta eksik görünen Teori girdisi olarak özel bir tip geçirme sorusunu açıkça ele almaktadır.
JD Cain

1
Bu tam olarak aradığım kullanım durumuydu ve karmaşık bir türü parametre olarak bir Teoriye nasıl aktaracağım. Mükemmel çalışıyor! Bu, MVP modellerini test etmek için gerçekten karşılığını veriyor. Artık bir Görünümün birçok farklı örneğini her tür durumda ayarlayabilir ve hepsini, Presenter yöntemlerinin bu görünüm üzerindeki etkilerini test eden aynı Teoriye aktarabilirim. Sevdim!
Denis M. Kitchen

10

Anonim nesne dizileri oluşturmak, verileri oluşturmanın en kolay yolu değildir, bu yüzden projemde bu deseni kullandım

Önce bazı yeniden kullanılabilir, paylaşılan sınıfları tanımlayın

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

}

Artık bireysel test ve üye verilerinizin yazılması daha kolay ve daha temiz ...

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

String Descriptionözelliği, birçok test durumunuzdan biri başarısız olduğunda kendinize bir kemik atmanızdır.


1
Bunu severim; Çok karmaşık bir nesne için gerçek bir potansiyele sahip. Basit bir JSON nesnesini geçirebilir, serisini kaldırabilir ve bir test yinelemesi için verileri oluşturabilirim. Aferin.
Gustyn

1
IsValid Test yönteminin parametreleri karışık değil mi - IsValid (içerik maddesi, exprectedResult, testDescription) olması gerekmez mi?
pastacool

3

Bu şekilde deneyebilirsiniz:

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

Test verilerini tutmak için başka bir sınıf oluşturun:

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

İhtiyaçlarım için sadece bazı testler aracılığıyla bir dizi 'test kullanıcısı' çalıştırmak istedim - ancak [ClassData] vb. İhtiyaç duyduğum şey için aşırı görünüyordu (çünkü öğelerin listesi her test için yerelleştirildi).

Bu yüzden, testin içindeki bir diziyle - dışarıdan indekslenmiş olarak aşağıdakileri yaptım:

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

Bu, testin amacını açık tutarken amacıma ulaştı. Sadece dizinleri senkronize tutmanız gerekiyor, hepsi bu.

Sonuçlarda güzel görünüyor, daraltılabilir ve bir hata alırsanız belirli bir örneği yeniden çalıştırabilirsiniz:

görüntü açıklamasını buraya girin


"Sonuçlarda güzel görünüyor, kapatılabilir ve bir hata alırsanız belirli bir örneği yeniden çalıştırabilirsiniz". Çok güzel bir nokta. Bunun en büyük dezavantajı MemberData, testi belirli bir test girdisi ile görememeniz veya çalıştıramamanızdır. Berbat.
Oliver Pearmain

Aslında, MemberDataeğer kullanırsanız TheoryDatave isteğe bağlı olarak bunun mümkün olduğunu anladım IXunitSerializable. Daha fazla bilgi ve örnekler burada ... github.com/xunit/xunit/issues/429#issuecomment-108187109
Oliver

1

Sorununuzu bu şekilde çözdüm, aynı senaryoya sahiptim. Bu nedenle, özel nesnelerle ve her çalışmada farklı sayıda nesneyle aynı hizada.

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

Bu benim birim testim , params parametresine dikkat edin . Bu, farklı sayıda nesne gönderilmesine izin verir. Ve şimdi DeviceTelemetryTestData sınıfım:

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

Umarım yardımcı olur !


-1

Sanırım burada yanılıyorsun. XUnit Theoryözniteliğinin gerçekte ne anlama geldiği: Bu işlevi, test edilen işlevin aldığı parametreler olarak özel / rastgele değerler göndererek test etmek istiyorsunuz. Yani örneğin bir sonraki özniteliği olarak tanımladığımız ne anlama geldiğini: InlineData, PropertyData, ClassData, vb .. bu parametreler için kaynak olacaktır. Bu, bu parametreleri sağlamak için kaynak nesneyi oluşturmanız gerektiği anlamına gelir. Senin durumunda ClassData, kaynak olarak nesneyi kullanmalısın . Ayrıca - lütfen aşağıdakilerden ClassDatadevraldığına dikkat edin : IEnumerable<>- bu, her seferinde başka bir oluşturulan parametreler kümesinin, IEnumerable<>değerler üretene kadar test edilmeyen işlev için gelen parametreler olarak kullanılacağı anlamına gelir .

Örnek burada: Tom DuPont .NET

Örnek yanlış olabilir - xUnit'i uzun süre kullanmadım

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.