Autofixture: Proposal untuk GenerationDepthBehavior.

Dibuat pada 1 Mei 2018  ·  12Komentar  ·  Sumber: AutoFixture/AutoFixture

Berikut adalah proposal perilaku pembatas Kedalaman generasi.

fixture.Behaviors.Add(New GenerationDepthBehavior(2));

Harus menerapkan kembali ComposeIfMultiple untuk pengujian karena itu internal.

```c#
GenerationDepthBehavior kelas publik : ISpecimenBuilderTransformation
{
private const int DefaultGenerationDepth = 1;
private readonly int generationDepth;

public GenerationDepthBehavior() : this(DefaultGenerationDepth)
{
}

public GenerationDepthBehavior(int generationDepth)
{
    if (generationDepth < 1)
        throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0.");

    this.generationDepth = generationDepth;
}


public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
    if (builder == null) throw new ArgumentNullException(nameof(builder));

    return new GenerationDepthGuard(builder, new GenerationDepthHandler(), this.generationDepth);
}

}

antarmuka publik IGenerationDepthHandler
{

object HandleGenerationDepthLimitRequest(object request, IEnumerable<object> recordedRequests, int depth);

}

DepthSeededRequest kelas publik : SeededRequest
{
public int Kedalaman { dapatkan; }
public DepthSeededRequest(permintaan objek, seed objek, kedalaman int): base(permintaan, seed)
{
Kedalaman = kedalaman;
}
}

GenerationDepthGuard kelas publik : ISpecimenBuilderNode
{
ThreadLocal yang hanya bisa dibaca pribadi> requestByThread
= ThreadLocal baru>(() => Tumpukan baru());

private Stack<DepthSeededRequest> GetMonitoredRequestsForCurrentThread() => this.requestsByThread.Value;


public GenerationDepthGuard(ISpecimenBuilder builder)
    : this(builder, EqualityComparer<object>.Default)
{
}

public GenerationDepthGuard(
    ISpecimenBuilder builder,
    IGenerationDepthHandler depthHandler)
    : this(
        builder,
        depthHandler,
        EqualityComparer<object>.Default,
        1)
{
}

public GenerationDepthGuard(
    ISpecimenBuilder builder,
    IGenerationDepthHandler depthHandler,
    int generationDepth)
    : this(
        builder,
        depthHandler,
        EqualityComparer<object>.Default,
        generationDepth)
{
}


public GenerationDepthGuard(ISpecimenBuilder builder, IEqualityComparer comparer)
{
    this.Builder = builder ?? throw new ArgumentNullException(nameof(builder));
    this.Comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
    this.GenerationDepth = 1;
}


public GenerationDepthGuard(
    ISpecimenBuilder builder,
    IGenerationDepthHandler depthHandler,
    IEqualityComparer comparer)
    : this(
    builder,
    depthHandler,
    comparer,
    1)
{
}

public GenerationDepthGuard(
    ISpecimenBuilder builder,
    IGenerationDepthHandler depthHandler,
    IEqualityComparer comparer,
    int generationDepth)
{
    if (builder == null) throw new ArgumentNullException(nameof(builder));
    if (depthHandler == null) throw new ArgumentNullException(nameof(depthHandler));
    if (comparer == null) throw new ArgumentNullException(nameof(comparer));
    if (generationDepth < 1)
        throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0.");

    this.Builder = builder;
    this.GenerationDepthHandler = depthHandler;
    this.Comparer = comparer;
    this.GenerationDepth = generationDepth;
}


public ISpecimenBuilder Builder { get; }

public IGenerationDepthHandler GenerationDepthHandler { get; }

public int GenerationDepth { get; }

public int CurrentDepth { get; }

public IEqualityComparer Comparer { get; }

protected IEnumerable RecordedRequests => this.GetMonitoredRequestsForCurrentThread();

public virtual object HandleGenerationDepthLimitRequest(object request, int currentDepth)
{
    return this.GenerationDepthHandler.HandleGenerationDepthLimitRequest(
        request,
        this.GetMonitoredRequestsForCurrentThread(), currentDepth);
}

public object Create(object request, ISpecimenContext context)
{
    if (request is SeededRequest)
    {
        int currentDepth = -1;

        var requestsForCurrentThread = this.GetMonitoredRequestsForCurrentThread();

        if (requestsForCurrentThread.Count > 0)
        {
            currentDepth = requestsForCurrentThread.Max(x => x.Depth) + 1;
        }

        DepthSeededRequest depthRequest = new DepthSeededRequest(((SeededRequest)request).Request, ((SeededRequest)request).Seed, currentDepth);

        if (depthRequest.Depth >= this.GenerationDepth)
        {
            return HandleGenerationDepthLimitRequest(request, depthRequest.Depth);
        }

        requestsForCurrentThread.Push(depthRequest);
        try
        {
            return this.Builder.Create(request, context);
        }
        finally
        {
            requestsForCurrentThread.Pop();
        }
    }
    else
    {
        return this.Builder.Create(request, context);
    }
}

public virtual ISpecimenBuilderNode Compose(
    IEnumerable<ISpecimenBuilder> builders)
{
    var composedBuilder = ComposeIfMultiple(
        builders);
    return new GenerationDepthGuard(
        composedBuilder,
        this.GenerationDepthHandler,
        this.Comparer,
        this.GenerationDepth);
}

internal static ISpecimenBuilder ComposeIfMultiple(IEnumerable<ISpecimenBuilder> builders)
{
    ISpecimenBuilder singleItem = null;
    List<ISpecimenBuilder> multipleItems = null;
    bool hasItems = false;

    using (var enumerator = builders.GetEnumerator())
    {
        if (enumerator.MoveNext())
        {
            singleItem = enumerator.Current;
            hasItems = true;

            while (enumerator.MoveNext())
            {
                if (multipleItems == null)
                {
                    multipleItems = new List<ISpecimenBuilder> { singleItem };
                }

                multipleItems.Add(enumerator.Current);
            }
        }
    }

    if (!hasItems)
    {
        return new CompositeSpecimenBuilder();
    }

    if (multipleItems == null)
    {
        return singleItem;
    }

    return new CompositeSpecimenBuilder(multipleItems);
}

public virtual IEnumerator<ISpecimenBuilder> GetEnumerator()
{
    yield return this.Builder;
}

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

}

GenerationDepthHandler kelas publik : IGenerationDepthHandler
{
objek publik HandleGenerationDepthLimitRequest(
permintaan objek,
dapat dihitungrecordRequests, kedalaman int)
{
kembalikan OmitSpecimen baru();
}
}
```

question

Komentar yang paling membantu

@malyemire1 Apakah Anda baik-baik saja jika kami menunda ini untuk saat ini dan menerapkannya nanti hanya jika kami melihat banyak permintaan untuk fitur ini?

Maaf kami tidak dapat memasukkan setiap fitur yang diinginkan ke inti Agak sulit untuk menjaga keseimbangan antara fitur yang disediakan dan jumlah kode yang kami pertahankan di sini. Sepertinya tidak akan menjadi masalah besar bagi Anda untuk menerapkan fitur ad-hoc ini dalam solusi Anda, sehingga Anda tidak diblokir.

Terima kasih sekali lagi atas proposal Anda!

Semua 12 komentar

Halo, terima kasih telah berbagi ide! :+1:

Bisakah Anda menjelaskan alasan di balik fitur ini? Apakah Anda memiliki skenario tertentu yang menurut Anda berguna?

Hai, Saya menggunakannya terutama untuk entitas ORM atau kelas api eksternal di mana kedalaman dapat menyebar tanpa batas tetapi tidak rekursif.
Saya menggunakan AutoFixture untuk Tes Unit, Tes Integrasi dan Tes Fungsional. Tes unit untuk fungsionalitas kerangka kerja perusahaan inti. Tes integrasi dengan tes untuk unit kerja yang kompleks dengan pola repositori dan akses ke database pengujian. Tes fungsional dengan akses ke seluruh aplikasi.

Saya telah meletakkannya langsung di AutoMoqDataAttribute

```c#
AutoMoqDataAttribute kelas publik : AutoDataAttribute
{
///


/// Konstruktor default.
///

/// Default ke 0 untuk kedalaman generasi tak terbatas.
AutoMoqDataAttribute publik (int generationDepth = 0)
: dasar(() =>
{
Fixture IFixture = Fixture baru().Customize(New AutoMoqCustomization());

        fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
                .ForEach(b => fixture.Behaviors.Remove(b));

        fixture.Behaviors.Add(new OmitOnRecursionBehavior());

        if (generationDepth > 0)
        {
            fixture.Behaviors.Add(new GenerationDepthBehavior(generationDepth));
        }

        return fixture;
    })
{
}

}
```

Saya juga menyarankan untuk membuat ComposeIfMultiple publik. Itu akan membuat implementasi perilaku kustom lebih mudah.

Saya mengerti apa yang dilakukan perilaku, pertanyaan utamanya adalah tujuan dan skenario.

Hai, Saya menggunakannya terutama untuk entitas ORM atau kelas api eksternal di mana kedalaman dapat menyebar tanpa batas tetapi tidak rekursif.

Jadi, sejauh yang saya mengerti, tujuan utama yang Anda tuju adalah kinerja. Dari perspektif fungsional semuanya bekerja dengan baik dan Anda hanya ingin menyimpan beberapa kutu CPU.

Agak sulit untuk menilai apakah kami ingin mengirimkan fitur ini secara langsung atau tidak. Saya mengerti bahwa Anda mungkin memang memiliki model tipe dengan sarang yang sangat dalam dan di sana kemenangan kinerjanya signifikan. Tetapi ada juga beberapa kelemahan dengannya:

  • itu merusak transparansi pengujian unit Anda. Sekarang Anda perlu mengetahui level objek yang diakses terdalam selama pengujian. Jika nanti Anda mengubah implementasi, pengujian mungkin mulai rusak karena nilai default dan Anda mungkin perlu memperbarui level.
  • itu meningkatkan pemeliharaan pengujian, karena sekarang Anda perlu melacak fakta bahwa grafik mungkin tidak diinisialisasi seluruhnya.

Tidak masalah jika dalam tim Anda, Anda memerlukan fitur ini dan Anda setuju untuk menggunakannya. Namun, saya berpendapat untuk menggunakannya secara umum karena sangat memperumit banyak hal.

Ada banyak skenario potensial dan AutoFixture mencoba mengirimkan fitur yang paling umum saja. Bit yang lebih maju dicakup oleh kemampuan ekstensibilitas. Dalam kasus khusus ini saya akan mengatakan bahwa fitur itu tidak umum untuk menjadikannya bagian dari produk. Poin lainnya adalah implementasinya sangat kecil (lihat di bawah), jadi seharusnya tidak menjadi masalah untuk mengimplementasikannya di proyek pengujian Anda secara lokal.

Saya juga menyarankan untuk membuat ComposeIfMultiple publik. Itu akan membuat implementasi perilaku kustom lebih mudah.

Sudah dibahas #657.


Omong-omong, implementasi mungkin sedikit disederhanakan (jika saya tidak melewatkan sesuatu):
```c#
GenerationDepthBehavior kelas publik: ISpecimenBuilderTransformation
{
public int Kedalaman { dapatkan; }

public GenerationDepthBehavior(int depth)
{
    Depth = depth;
}

public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
    return new RecursionGuard(builder, new OmitOnRecursionHandler(), new IsSeededRequestComparer(), Depth);
}

private class IsSeededRequestComparer : IEqualityComparer
{
    bool IEqualityComparer.Equals(object x, object y)
    {
        return x is SeededRequest && y is SeededRequest;
    }

    int IEqualityComparer.GetHashCode(object obj)
    {
        return obj is SeededRequest ? 0 : EqualityComparer<object>.Default.GetHashCode(obj);
    }
}

}
```


@moodmosaic Bisakah Anda juga membagikan pendapat Anda? 😉

Contoh yang ditampilkan di https://github.com/AutoFixture/AutoFixture/issues/1032#issuecomment -385724150 adalah cara yang lebih disukai untuk menggunakan perilaku ad-hoc ini . Pada titik ini, saya tidak berpikir kita harus mengubah algoritma bawaan.


Apa yang bisa kita pertimbangkan, bagaimanapun, adalah untuk

  • gunakan PRNG yang dapat dipisah (yang saat ini tidak dapat dipisah)
  • tambahkan tes keacakan (ikuti https://github.com/hedgehogqa/haskell-hedgehog/issues/125, misalnya)
  • tolak ukur

lalu , kami dapat mempertimbangkan untuk melakukan pengoptimalan lebih lanjut, seperti ini.

@moodmosaic Tampaknya sarannya bukan untuk mengubah perilaku default, tetapi menjadikan GenerationDepthBehavior sebagai bagian dari perpustakaan AutoFixture (sehingga Anda dapat mengaktifkannya sesuai permintaan). Jadi apa pendapat Anda tentang hal ini? :mengedip:

@zvirja Sebenarnya bukan karena alasan kinerja. Masalah dengan propagasi tak terbatas adalah tes yang gagal tanpa pengecualian memori.

@zvirja , @malyemire1 , kecuali jika perilaku ad-hoc ini membahas berbagai skenario, saya rasa itu tidak harus ada di dalamnya.


Saya tidak dapat menemukan MCVE , tetapi AFAICT, masalahnya berasal dari pengujian otomatis yang sebenarnya bukan pengujian unit (misalnya pengujian batas/integrasi).

AutoFixture tidak terutama dirancang untuk menangani tes integrasi, sehingga beberapa perilaku ad-hoc (seperti yang ditunjukkan di sini) dijamin: 1: Saya tidak berpikir itu harus menjadi bagian dari perpustakaan inti, meskipun.

@malyemire1 Apakah Anda baik-baik saja jika kami menunda ini untuk saat ini dan menerapkannya nanti hanya jika kami melihat banyak permintaan untuk fitur ini?

Maaf kami tidak dapat memasukkan setiap fitur yang diinginkan ke inti Agak sulit untuk menjaga keseimbangan antara fitur yang disediakan dan jumlah kode yang kami pertahankan di sini. Sepertinya tidak akan menjadi masalah besar bagi Anda untuk menerapkan fitur ad-hoc ini dalam solusi Anda, sehingga Anda tidak diblokir.

Terima kasih sekali lagi atas proposal Anda!

Menutup masalah ini sebagai bagian dari pembersihan. Terima kasih lagi untuk berbagi ide! 😉

@malyemire1 Saya 100% mengerti kasus penggunaan.
Apakah ini sesuatu yang dapat Anda sediakan dalam paket nuget?

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

ploeh picture ploeh  ·  3Komentar

malylemire1 picture malylemire1  ·  7Komentar

zvirja picture zvirja  ·  8Komentar

Accc99 picture Accc99  ·  4Komentar

DeafLight picture DeafLight  ·  5Komentar