Autofixture: Pendekatan untuk menghasilkan spesimen Acak berdasarkan Kustomisasi

Dibuat pada 8 Jan 2018  ·  11Komentar  ·  Sumber: AutoFixture/AutoFixture

Hai, saya ingin dapat menghasilkan nilai yang berbeda berdasarkan ICustomization menggunakan ISpecimenBuilder.CreateMany . Saya bertanya-tanya apa solusi terbaik karena AutoFixture akan menghasilkan nilai yang sama untuk semua entitas.

```c#
FooCustomization kelas publik : ICustomization
{
kustomisasi kekosongan publik (perlengkapan IFixture)
{
var spesimen = fixture.Build()
.AbaikanPropertiOtomatis()
.Dengan(x => x.CreationDate, DateTime.Now)
.Dengan(x => x.Identifier, Guid.NewGuid().ToString().Substring(0, 6)) // Haruskah menghasilkan nilai yang berbeda
.Dengan(x => x.Mail, $"[email protected]")
.Membuat();

        fixture.Register(() => specimen);
}

}
```

PS.: Tujuan utamanya adalah untuk mengurangi jumlah kode pada pengujian saya, jadi saya harus menghindari panggilan With() untuk menyesuaikan nilai untuk setiap pengujian. Apakah ada cara yang tepat untuk melakukan ini?

Pembaruan: Saya baru saja membaca tanggapan ini dari @ploeh : https://github.com/AutoFixture/AutoFixture/issues/361#issuecomment -86873060 mungkin itu yang saya cari.
Pendekatan ini memiliki banyak kelemahan: Pertama tampaknya benar-benar kontra intuitif untuk memanggil Create<List<Foo>>() karena agak mengalahkan tujuan dari apa yang diharapkan dari CreateMany<Foo> ; yang akan menghasilkan Daftar berukuran hardcoded> (?). Kelemahan lain adalah bahwa saya harus memiliki dua penyesuaian untuk setiap entitas; satu untuk membuat koleksi kustom dan satu lagi untuk membuat satu instance, karena kami mengganti perilaku Create<T> untuk membuat koleksi.

question

Semua 11 komentar

Ini sepertinya kandidat yang baik untuk pertanyaan Stack Overflow . Tidak ada yang salah dengan mengajukan pertanyaan di sini, tetapi saya pikir pertanyaan ini adalah jenis jawaban yang mungkin bermanfaat bagi orang lain juga, dan lebih mudah untuk menemukan pertanyaan dan jawaban di Stack Overflow dibandingkan dengan masalah GitHub. Pertama, Google melakukan pekerjaan yang baik dalam mengindeks pertanyaan Stack Overflow, sedangkan tampaknya peringkat (tertutup) masalah GitHub lebih rendah ...

@ploeh saya setuju dengan Anda ....
Saya baru saja membuat pertanyaan SO berdasarkan Masalah ini. Ini adalah tautan untuk referensi di masa mendatang 👍

@ploeh Terima kasih telah menjawab pertanyaan di SO :blush:

@thiagomajesk Sebenarnya tidak ada masalah dengan menjawab pertanyaan di sini. Alasan mengapa Mark menyarankan SO adalah kemampuan pencarian yang ditawarkannya. Juga saat ini Mark sedang memantau SO, jadi jika Anda memposting pertanyaan di sana, ada lebih banyak peluang untuk mendapatkan keahliannya (seperti yang Anda dapatkan saat ini):sweat_smile:

@zvirja Terima kasih telah mengklarifikasi itu

@zvirja Saya akan menutup pertanyaan pada SO berdasarkan jawaban bagus @ploeh .
Tetapi saya ingin meminta sedikit informasi lebih lanjut tentang detail implementasi yang satu ini.
Karena saya tidak ingin menyimpang lebih jauh dari subjek, saya pikir akan lebih baik untuk menanyakan hal ini secara spesifik di sini. Jadi saya memiliki implementasi ini:

```c#
UniqueShortGuidBuilder kelas publik: ISpecimenBuilder
{
propName string hanya-baca pribadi;
panjang int hanya-baca pribadi;

public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
{
    propName = ((MemberExpression)expr.Body).Member.Name;
    this.lenght = lenght;
}

Buat objek publik (permintaan objek, konteks ISpecimenContext)
{
var pi = permintaan sebagai PropertyInfo;

if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
    return new NoSpecimen();

return Guid.NewGuid().ToString().Substring(0, lenght);

}
```

Saya mengharapkan bahwa saat membuat banyak objek dengan AutoFixture, ISpecimenBuilder.Create akan dipanggil beberapa kali, menghasilkan Panduan berbeda untuk entitas saya; tapi ternyata ini tidak benar dan mereka sebenarnya berbagi hal yang sama.

Yah, saya baru saja menguji kode Anda dan berfungsi persis seperti yang diinginkan:

```c#
kelas publik Foo
{
string publik PropertyToCustomize { dapatkan; mengatur; }
}

UniqueShortGuidBuilder kelas publik: ISpecimenBuilder
{
propName string hanya-baca pribadi;
panjang int hanya-baca pribadi;

public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
{
    propName = ((MemberExpression) expr.Body).Member.Name;
    this.lenght = lenght;
}

public object Create(object request, ISpecimenContext context)
{
    var pi = request as PropertyInfo;

    if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
        return new NoSpecimen();

    return Guid.NewGuid().ToString().Substring(0, lenght);
}

}

[Fakta]
publik void TestCustomization()
{
var perlengkapan = Perlengkapan baru();
fixture.Customizations.Add (UniqueShortGuidBuilder baru)(x => x.PropertyToCustomize, 5));

var manyFoos = fixture.CreateMany<Foo>(10);

var uniqueIds = manyFoos
    .Select(x => x.PropertyToCustomize)
    .Where(x => x.Length == 5)
    .Distinct()
    .ToArray();
Assert.Equal(10, uniqueIds.Length);

}
```

Oleh karena itu, saya ingin sekali lagi meminta Anda untuk melihat perbedaan kode Anda yang sebenarnya, sehingga kami dapat memahami mengapa perbedaan itu penting

@zvirja Itu aneh. Kode di bawah ini adalah repro minimal dari masalah yang saya alami.
Jika saya menghapus Fixture.Customize(new FooCustomization()); tes akan lulus, jika tidak, tidak.

```c#
menggunakan Sistem;
menggunakan Microsoft.VisualStudio.TestTools.UnitTesting;
menggunakan AutoFixture.Kernel;
menggunakan System.Linq.Expressions;
menggunakan System.Reflection;
menggunakan AutoFixture;
menggunakan System.Linq;

namespace UnitTestProject1
{
kelas publik Foo
{
string publik PropertyToCustomize { dapatkan; mengatur; }
}

public class FooCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        var specimen = fixture.Build<Foo>()
            .OmitAutoProperties()
            .With(x => x.PropertyToCustomize)
            .Create();

        fixture.Register(() => specimen);
    }
}

public class UniqueShortGuidBuilder<TEntity> : ISpecimenBuilder
{
    private readonly string propName;
    private readonly int lenght;

    public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
    {
        propName = ((MemberExpression)expr.Body).Member.Name;
        this.lenght = lenght;
    }

    public object Create(object request, ISpecimenContext context)
    {
        var pi = request as PropertyInfo;

        if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
            return new NoSpecimen();

        return Guid.NewGuid().ToString().Substring(0, lenght);
    }
}

[TestClass]
public class UnitTest1
{
    public static Fixture Fixture { get; private set; }

    [ClassInitialize]
    public static void ClassInitialize(TestContext context)
    {
        Fixture = new Fixture();
        Fixture.Customizations.Add(new UniqueShortGuidBuilder<Foo>(x => x.PropertyToCustomize, 5));
        Fixture.Customize(new FooCustomization());
    }

    [TestMethod]
    public void TestMethod1()
    {
        var manyFoos = Fixture.CreateMany<Foo>(10);

        var uniqueIds = manyFoos
            .Select(x => x.PropertyToCustomize)
            .Where(x => x.Length == 5)
            .Distinct()
            .ToArray();

        Assert.AreEqual(10, uniqueIds.Length);
    }
}

}
```

Rekomendasi: Jika Anda mengandalkan mengambil _n_ karakter pertama dari string GUID, jangan beri nama kelas UniqueShortGuidBuilder . Nilainya mungkin cukup unik jika panjangnya 5, tetapi jika Anda menyetelnya ke 1, Anda mungkin akan melihat tabrakan. Bagaimanapun, keunikan tidak dijamin.

Jika Anda hanya peduli dengan panjang string, ditambah keunikan yang tidak dijamin, mengapa tidak meminta AutoFixture untuk angka antara, katakanlah, 0 dan 99.999, dan ubah menjadi string? IIRC, angka bersifat unik hingga rentang habis.

Jika saya menghapus Fixture.Customize(new FooCustomization()); tes akan lulus, jika tidak.

Nah, sebenarnya itulah perilaku yang diharapkan jika Anda meninjau situasinya lebih hati-hati Silakan lihat balasan saya di #962. Implementasi FooCustomization disediakan membuat instance dari tipe Foo dan mengonfigurasi perlengkapan untuk selalu mengembalikan instance itu. Oleh karena itu, ketika nanti Anda membuat beberapa instance tipe Foo , setiap kali objek yang sama dikembalikan dan builder Anda tidak dipanggil lebih lanjut.

Jika Anda ingin instance baru dibuat untuk setiap kali Anda meminta Foo , gunakan Customize<> API seperti yang saya sarankan di sini .

@ploeh Terima kasih atas tipnya, tetapi kode itu hanyalah contoh kasar untuk tujuan pengujian. Untuk kasus nyata, panjangnya tidak akan terlalu pendek untuk memengaruhi keunikan Guid .

Jika Anda hanya peduli dengan panjang string, ditambah keunikan yang tidak dijamin, mengapa tidak meminta AutoFixture untuk angka antara, katakanlah, 0 dan 99.999, dan ubah menjadi string?

Saya yakin ini adalah solusi yang bagus untuk sebagian besar kasus, tetapi saya tidak dapat melakukannya untuk kasus khusus ini karena validasi alfanumerik di sistem saya. Selain itu, saya memiliki validasi yang lebih kompleks yang melibatkan bidang itu.

Nah, sebenarnya itulah perilaku yang diharapkan jika Anda meninjau situasinya lebih hati-hati

@zvirja Jelas setelah balasan Anda di #962. Tetapi bagi saya perilaku itu tidak jelas, karena tidak ada dokumentasi di muka (eksplisit) tentang AutoFixture (seperti yang saya tunjukkan di sini ) 😁.

Sebagai penutup, terima kasih atas kesabaran dan semua penjelasan yang luar biasa.
Saya yakin ini akan berguna untuk orang lain juga di masa depan .

Oke, cukup adil, saya mungkin memiliki kecenderungan untuk mengambil hal-hal yang terlalu literal

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

ploeh picture ploeh  ·  3Komentar

zvirja picture zvirja  ·  4Komentar

Eldar1205 picture Eldar1205  ·  5Komentar

Ridermansb picture Ridermansb  ·  4Komentar

ecampidoglio picture ecampidoglio  ·  7Komentar