Meneruskan parameter kompleks ke [Teori]

98

Xunit memiliki fitur yang bagus : Anda dapat membuat satu pengujian dengan Theoryatribut dan memasukkan data ke dalam InlineDataatribut, dan xUnit akan menghasilkan banyak pengujian, dan menguji semuanya.

Saya ingin memiliki sesuatu seperti ini, tetapi parameter untuk metode saya tidak 'data sederhana' (seperti string, int, double), tapi daftar kelas saya:

public static void WriteReportsToMemoryStream(
    IEnumerable<MyCustomClass> listReport,
    MemoryStream ms,
    StreamWriter writer) { ... }
zchpit
sumber
1
Panduan lengkap yang mengirimkan objek kompleks sebagai parameter ke Metode pengujian tipe kompleks dalam Pengujian unit
Iman Bahrampour

Jawaban:

138

Ada banyak xxxxDataatribut di XUnit. Lihat misalnya PropertyDataatribut.

Anda dapat mengimplementasikan properti yang mengembalikan IEnumerable<object[]>. Masing object[]- masing yang dihasilkan metode ini akan "dibongkar" sebagai parameter untuk satu panggilan ke [Theory]metode Anda .

Opsi lainnya adalah ClassData, yang berfungsi sama, tetapi memungkinkan untuk dengan mudah berbagi 'generator' antara pengujian di kelas / ruang nama yang berbeda, dan juga memisahkan 'generator data' dari metode pengujian yang sebenarnya.

Lihat contoh berikut dari sini :

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

Contoh 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(); }
}
quetzalcoatl
sumber
@dcastro: ya, saya sebenarnya mencari beberapa di dokumen xunit asli
quetzalcoatl
2
@ Nick: Saya setuju itu mirip dengan PropertyData, tetapi juga, Anda telah menunjukkan alasan untuk itu: static. Itulah mengapa saya tidak mau. ClassData adalah saat Anda ingin melepaskan diri dari statika. Dengan demikian, Anda dapat menggunakan kembali (yaitu membuat sarang) generator lebih mudah.
quetzalcoatl
1
Ada ide apa yang terjadi dengan ClassData? Saya tidak dapat menemukannya di xUnit2.0, untuk saat ini, saya menggunakan MemberData dengan metode statis, yang membuat instance kelas baru, dan mengembalikannya.
Erti-Chris Eelmaa
14
@Erti, gunakan [MemberData("{static member}", MemberType = typeof(MyClass))]untuk mengganti ClassDataatribut.
Junle Li
6
Pada C # 6 itu akan merekomendasikan untuk menggunakan nameofkata kunci daripada hardcoding nama properti (rusak dengan mudah tapi diam-diam).
sara
40

Untuk memperbarui jawaban @ Quetzalcoatl: Atribut [PropertyData]telah digantikan [MemberData]yang mengambil nama string dari setiap metode statis, bidang, atau properti yang mengembalikan IEnumerable<object[]>. (Saya merasa sangat senang memiliki metode iterator yang benar-benar dapat menghitung kasus uji satu per satu, menghasilkannya saat dihitung.)

Setiap elemen dalam urutan yang dikembalikan oleh enumerator adalah an object[]dan setiap array harus memiliki panjang yang sama dan panjang tersebut harus merupakan jumlah argumen untuk kasus pengujian Anda (dianotasi dengan atribut [MemberData]dan setiap elemen harus memiliki jenis yang sama dengan parameter metode yang sesuai (Atau mungkin bisa jadi tipe convertible, saya tidak tahu.)

(Lihat catatan rilis untuk xUnit.net Maret 2014 dan patch aktual dengan kode contoh .)

davidbak.dll
sumber
2
@davidbak Codplexnya hilang. Tautan tidak berfungsi
Kishan Vaishnav
11

Membuat array objek anonim bukanlah cara termudah untuk membangun data jadi saya menggunakan pola ini dalam proyek saya

Pertama, tentukan beberapa kelas bersama yang dapat digunakan kembali

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

}

Sekarang pengujian individu dan data anggota Anda lebih mudah ditulis dan lebih bersih ...

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

DescriptionProperti string akan memberatkan diri Anda sendiri saat salah satu dari banyak kasus pengujian Anda gagal

perintah
sumber
1
Saya suka ini; ini memiliki potensi nyata untuk objek yang sangat kompleks. Saya harus memvalidasi validasi pada 90+ properti. Saya bisa mengirimkan objek JSON sederhana, deserialisasi, dan menghasilkan data untuk iterasi pengujian. Kerja bagus.
Gustyn
1
bukankah parameter untuk Metode Tes IsValid tercampur - bukankah seharusnya IsValid (bahan, exprectedResult, testDescription)?
pastacool
9

Misalkan kita memiliki kelas Mobil kompleks yang memiliki kelas Pabrikan:

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

Kami akan mengisi dan lulus kelas Mobil ke tes Teori.

Jadi buat kelas 'CarClassData' yang mengembalikan instance kelas Car seperti di bawah ini:

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

Saatnya membuat metode pengujian (CarTest) dan menentukan mobil sebagai parameter:

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

tipe kompleks dalam teori

Semoga berhasil

Iman Bahrampour
sumber
3
Jawaban ini secara eksplisit menjawab pertanyaan tentang meneruskan tipe kustom sebagai input Teori yang tampaknya hilang dari jawaban yang dipilih.
JD Cain
1
Ini persis kasus penggunaan yang saya cari yang merupakan cara meneruskan tipe kompleks sebagai parameter ke Teori. Bekerja dengan sempurna! Ini benar-benar terbayar untuk menguji pola MVP. Sekarang saya dapat mengatur banyak contoh berbeda dari Tampilan dalam semua jenis status dan meneruskan semuanya ke dalam Teori yang sama yang menguji efek yang dimiliki metode Presenter pada tampilan itu. Suka!
Denis M. Kitchen
3

Anda bisa mencoba cara ini:

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

Buat kelas lain untuk menyimpan data pengujian:

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;
      }
   }
}
Sandy_Vu
sumber
1

Untuk kebutuhan saya, saya hanya ingin menjalankan serangkaian 'pengguna uji' melalui beberapa pengujian - tetapi [ClassData] dll. Tampaknya berlebihan untuk apa yang saya butuhkan (karena daftar item dilokalkan ke setiap pengujian).

Jadi saya melakukan yang berikut, dengan array di dalam tes - diindeks dari luar:

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

Ini mencapai tujuan saya, sekaligus menjaga tujuan pengujian tetap jelas. Anda hanya perlu menjaga indeks tetap sinkron, tetapi itu saja.

Kelihatannya bagus di hasil, ini dapat diciutkan dan Anda dapat menjalankan kembali contoh tertentu jika Anda mendapatkan kesalahan:

masukkan deskripsi gambar di sini

Simon_Weaver
sumber
"Kelihatannya bagus di hasil, ini dapat diciutkan dan Anda dapat menjalankan kembali contoh tertentu jika Anda mendapatkan kesalahan". Poin yang sangat bagus. Kelemahan utama dari MemberDatatampaknya adalah Anda tidak dapat melihat atau menjalankan tes dengan input tes tertentu. Menyebalkan sekali.
Oliver Pearmain
Sebenarnya, saya baru saja mengetahui bahwa itu mungkin MemberDatajika Anda menggunakan TheoryDatadan secara opsional IXunitSerializable. Info lebih lanjut dan contoh di sini ... github.com/xunit/xunit/issues/429#issuecomment-108187109
Oliver Pearmain
1

Beginilah cara saya memecahkan masalah Anda, saya memiliki skenario yang sama. Jadi sejalan dengan objek khusus dan jumlah objek yang berbeda pada setiap proses.

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

Jadi ini unit test saya, perhatikan parameter params . Ini memungkinkan untuk mengirim sejumlah objek yang berbeda. Dan sekarang kelas DeviceTelemetryTestData saya :

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

Semoga membantu!

Max_Thom
sumber
-1

Saya kira Anda salah di sini. Apa Theorysebenarnya arti atribut xUnit : Anda ingin menguji fungsi ini dengan mengirimkan nilai khusus / acak sebagai parameter yang diterima oleh fungsi yang sedang diuji ini. Itu berarti bahwa apa yang Anda definisikan sebagai atribut berikutnya, seperti: InlineData, PropertyData, ClassData, dll .. akan menjadi sumber bagi mereka parameter. Itu berarti Anda harus membuat objek sumber untuk menyediakan parameter tersebut. Dalam kasus Anda, saya kira Anda harus menggunakan ClassDataobjek sebagai sumber. Juga - harap dicatat bahwa ClassDatamewarisi dari: IEnumerable<>- itu berarti setiap kali kumpulan parameter yang dihasilkan akan digunakan sebagai parameter masuk untuk fungsi yang sedang diuji hingga IEnumerable<>menghasilkan nilai.

Contoh di sini: Tom DuPont .NET

Contoh mungkin salah - Saya tidak menggunakan xUnit untuk waktu yang lama

Jasper
sumber