Autofixture: الطلب: أضف طريقة النسخ إلى IFixture

تم إنشاؤها على ٢٤ سبتمبر ٢٠٢٠  ·  9تعليقات  ·  مصدر: AutoFixture/AutoFixture

مقدمة

أرغب إذا كان لدى IFixture طريقة Clone أو شيء مشابه لإنشاء نسخة من الأداة.

في الوقت الحالي يمكنني إنشاء طريقة تمديد للقيام بذلك:

public static IFixture Clone( this IFixture fixture)
{
   var cloneFixture = new Fixture();

   cloneFixture.Behaviors.Clear();
   foreach ( var behavior in fixture.Behaviors)
   {
      cloneFixture.Behaviors.Add( behavior );
   }

   cloneFixture.Customizations.Clear();
   foreach ( var customization in fixture.Customizations )
   {
      cloneFixture.Customizations.Add( customization );
   }

   cloneFixture.OmitAutoProperties = fixture.OmitAutoProperties;
   cloneFixture.RepeatCount = fixture.RepeatCount;

   cloneFixture.ResidueCollectors.Clear();
   foreach ( var residueCollector in fixture.ResidueCollectors )
   {
      cloneFixture.ResidueCollectors.Add( residueCollector );
   }

   return cloneFixture;
}

لكني أشعر أن مثل هذه القدرة على عمل نسخة يجب أن تكون في المشروع نفسه.

تفاصيل

السيناريو الخاص بي هو أنني أقوم ببناء مثلين من فئة بها الكثير من المعلمات في الاختبار. بالإضافة إلى ذلك ، أريد أن يكون معلمة المُنشئ قيمة معينة لمثيل واحد وقيمة مُنشِئة مختلفة لمثيل مختلف. هناك عدة طرق يمكنني القيام بذلك ؛ الطريقة التي كنت أفعل بها ذلك هي:

fixture.Customizations.Add( new FilteringSpecimenBuilder(
            new FixedBuilder( mediaId ),
            new ParameterSpecification( 
                typeof( ObjectId ), 
                "mediaId" ) ) );
var mediaVM = fixture.Build<MediaViewModel>()   
                     .With( mvm => mvm.ParentMixerId, mixerId )
                     .With( mvm => mvm.TrackId, trackId )
                     .Create();

_ = fixture.Customizations.Remove( fixture.Customizations.Last() );

//...

سبب إزالة التخصيص هو أنني حاولت دون التفكير في أن التخصيص الأخير الذي تمت إضافته سيكون له أسبقية أعلى وسيتم استخدامه ؛ لكن لم يكن الأمر كذلك.

إذا حاولت تبسيط هذا بنوع من طريقة الامتداد مثل:

public static IFixture AddCustomization<T>( this IFixture fixture, T value, string name )
{
   fixture.Customizations.Add( new FilteringSpecimenBuilder(
         new FixedBuilder( value ),
         new ParameterSpecification(
            typeof( T ),
            name ) ) );
   return fixture;
}

قد تعمل في بعض الأماكن ولكنها لن تعمل في أماكن أخرى لأنه بعد بناء الكائن ، تحتوي الوحدة على تخصيص لم أعد أرغب فيه وأحتاج إلى إزالته.

أفضل عدم وجود خط لإزالة التخصيص (التخصيصات) المضافة. لذلك ، إذا كانت طريقة التمديد الخاصة بي قد صنعت نسخة من تركيباتي التي أقوم بإضافة تعديلات عليها إلى النسخة ، فإن إبداعاتي تعمل كما هو متوقع ولا يتم المساس بالتركيب الأصلي.

من الممكن أيضًا أن تعمل القدرة على إضافة التخصيص الذي يتم إزالته تلقائيًا بعد إنشاء الكائن.

أتمنى أن يكون كل هذا منطقيًا.

شكرا للنظر في مشكلتي: مبتسم:

feature request

التعليق الأكثر فائدة

ajorians كنت على وشك إغلاق المشكلة بسبب الخمول لكنني لم أستطع السير بجانب هذا المُنشئ العملاق.
علي أن أسأل ، هل فكرت في تغيير تصميمك؟

من خلال النظر إلى المُنشئ الخاص بك ، يمكنني رؤية العديد من المشكلات والحلول المحتملة لمشكلتك.

بادئ ذي بدء ، تقوم بخلط مفاهيم الكيانات الجديدة والقابلة للحقن.
الحل الأكثر وضوحًا هو نقل التبعيات القابلة للحقن (المعروفة أيضًا باسم واجهات الخدمة) إلى الخصائص وحقنها باستخدام حقن الممتلكات.
سيؤدي هذا إلى حل مشكلتك الفورية المتمثلة في الاضطرار إلى نسخ Fixture وسيجعل المُنشئ أكثر قابلية للإدارة.

الثاني هو معالجة رائحة كود الحقن المفرط .
هذا يعني الابتعاد عن الخدمات المشتركة الأكثر استخدامًا إلى تجريدات منفصلة.
من المفترض أن يساعد هذا التصميم الخاص بك على احترام SRP ومن خلال التمديد ، يجب أن يقلل المقدار الهائل من الإعداد الذي يتعين عليك القيام به في الوقت الحالي ، في اختباراتك.

نظرًا لأن الفصل يسمى ViewModel أفترض أن لديك تطبيق MVVM. من المحتمل أنه يمكنك فصل تطبيقك عن طريق تقديم نموذج مراسلة. ربما مجمع الحدث. تحتوي معظم أطر MVVM على عناصر مدمجة بحيث لا تضطر إلى القيام بالكثير من أعمال الإعداد ، للاستفادة منها.

اعلمني اذا كان هذا مفيدا لك.

ال 9 كومينتر

@ Majorians يوم جيد! من كيف فهمت مثالك ، يبدو أن لدينا بالفعل ما تحتاجه. تعمل طريقة التثبيت Build() في الواقع على إنشاء نسخة ثابتة من الرسم البياني الداخلي ، لذا يمكنك تطبيق تخصيصات "لمرة واحدة" أعلى AutoFixture دون الإصرار عليها.

مراجعة عرض الملعب هذا:

ج #
نموذج الطبقة العامة
{
معرف int العامة {get؛ يضع؛ }
اسم السلسلة العامة {get؛ يضع؛ }
}

[حقيقة]
عرض الفراغ العام ()
{
var fixture = تركيبات جديدة () ؛
المباراة(ج => ج مع (م => م ، 42)) ؛

var value1 = fixture.Create<Model>();
Assert.Equal(42, value1.Id);
Assert.NotNull(value1.Name);

var value2 = fixture.Build<Model>()
    .With(m => m.Id, (int genInt) => genInt * 2 /* make it always even */)
    .Without(x => x.Name)
    .Create();
Assert.NotEqual(42, value2.Id);
Assert.Equal(value2.Id % 2, 0);
Assert.Null(value2.Name);

var value3 = fixture.Create<Model>();
Assert.Equal(42, value3.Id);
Assert.NotNull(value3.Name);

}
""

دعنا نعرف ما إذا كان يلبي احتياجاتك. إذا لم يكن الأمر كذلك ، فيرجى وصف سبب عدم نجاحه بالنسبة لك بشكل أكثر تحديدًا ، حتى نتمكن من محاولة المساعدة.

شكرا!

مرحبًا zvirja ،

يوم جيد!

إذا جاز لي؛ لن يكون لفصلنا Model set ولن يمكن تحديده إلا من خلال المنشئ. لذلك إذا كنت تريد استبداله بهذا:

public class Model
{
   public Model( int id, string name )
   {
      Id = id;
      Name = name;
   }

   public int Id { get; }
   public string Name { get; }
}

آسف لعدم تحديد ذلك في الأصل.

لذا ، كما أفهم الأشياء ، يجب علي إضافة التخصيصات من أجل عمل معلمات المُنشئ. وهذه التخصيصات قبل Build() .

إذا كنت مهتمًا ببعض الأسباب التي تجعلنا نفعل الكثير في المنشئ وبدون محددات ، فيمكننا استخدام الكلمة الأساسية readonly والتأكد من أن متغيرات العضو قد تم تعيينها ولا يمكن إلغاء ضبطها بحلول الوقت الذي يتم فيه تعيين الطريقة مسمى.

أتمنى أن يكون ذلك منطقيًا بشكل أفضل. شكرا للمساعدة في هذا! : مبتسم:

ajorians هل أفهم مشكلتك بشكل صحيح ، أن لديك نموذجًا به حجج إدخال كبيرة ، لذلك ترغب في تخصيص واحدة منها فقط ، دون الحاجة إلى القلق بشأنه صراحة. حق؟

خلاف ذلك ، يمكنك دائمًا كتابة شيء مثل
c# var value2 = fixture.Build<Model>() .FromFactory((int id, string name) => new Model(id, name)) .Create();

لسوء الحظ ، لا يتسع نطاقه جيدًا مع تزايد عدد المعلمات 😟

ajorians هل أفهم مشكلتك بشكل صحيح ، أن لديك نموذجًا به حجج إدخال كبيرة ، لذلك ترغب في تخصيص واحدة منها فقط ، دون الحاجة إلى القلق بشأنه صراحة. حق؟

نعم ، هذا صحيح تمامًا.

لسوء الحظ ، لا يتسع نطاقه جيدًا مع تزايد عدد المعلمات 😟

لحسن الحظ ، يعمل Freeze مع العديد من فصولنا الدراسية مع عدد متزايد من المعلمات. لكن نعم ، الفئة التي كنت أفكر فيها حيث أستخدم التخصيصات للقيام بمعلمات المُنشئ لديها حاليًا أكثر من 30 معلمات:
image

ومن المحتمل أن ينمو 4 أو نحو ذلك قبل نهاية هذا العام.

أحصل على هذا قد يكون غير عادي.

لكن نعم ، لدي قدر كبير من حجج مُنشئ المدخلات المتزايدة وأود تخصيص واحدة فقط (أو القليل منها) دون التفكير في الباقي. بدون تسجيل هذا النوع أو تجميده للاختبار بأكمله. وبطريقة موجزة مثل إضافة التخصيص حوالي 5 سطور من التعليمات البرمجية.

لحسن الحظ أنا قادر على القيام بكل ذلك ؛ ولكن الطريقة التي قمت بها تضمنت إنشاء طريقة تمديد clone في كود العميل الخاص بي.

أتمنى أن يكون كل هذا منطقيًا. شكرا للنظر إلى هذا معي: مبتسم:

ajorians كنت على وشك إغلاق المشكلة بسبب الخمول لكنني لم أستطع السير بجانب هذا المُنشئ العملاق.
علي أن أسأل ، هل فكرت في تغيير تصميمك؟

من خلال النظر إلى المُنشئ الخاص بك ، يمكنني رؤية العديد من المشكلات والحلول المحتملة لمشكلتك.

بادئ ذي بدء ، تقوم بخلط مفاهيم الكيانات الجديدة والقابلة للحقن.
الحل الأكثر وضوحًا هو نقل التبعيات القابلة للحقن (المعروفة أيضًا باسم واجهات الخدمة) إلى الخصائص وحقنها باستخدام حقن الممتلكات.
سيؤدي هذا إلى حل مشكلتك الفورية المتمثلة في الاضطرار إلى نسخ Fixture وسيجعل المُنشئ أكثر قابلية للإدارة.

الثاني هو معالجة رائحة كود الحقن المفرط .
هذا يعني الابتعاد عن الخدمات المشتركة الأكثر استخدامًا إلى تجريدات منفصلة.
من المفترض أن يساعد هذا التصميم الخاص بك على احترام SRP ومن خلال التمديد ، يجب أن يقلل المقدار الهائل من الإعداد الذي يتعين عليك القيام به في الوقت الحالي ، في اختباراتك.

نظرًا لأن الفصل يسمى ViewModel أفترض أن لديك تطبيق MVVM. من المحتمل أنه يمكنك فصل تطبيقك عن طريق تقديم نموذج مراسلة. ربما مجمع الحدث. تحتوي معظم أطر MVVM على عناصر مدمجة بحيث لا تضطر إلى القيام بالكثير من أعمال الإعداد ، للاستفادة منها.

اعلمني اذا كان هذا مفيدا لك.

مرحبًا aivascu ،

ajorians كنت على وشك إغلاق القضية بسبب عدم النشاط

إذا كان هناك أي شيء يمكنني القيام به ، فأخبرني بذلك. نظرًا لكونه طلب ميزة يمكنني رؤيته حتى يتم تنفيذه ، فقد يكون هناك قدر كبير من عدم النشاط.

علي أن أسأل ، هل فكرت في تغيير تصميمك؟

في الوقت الحالي ، نفكر في أن نصبح تطبيقًا موشورًا . من شأن ذلك أن يساعد في بعض العناصر التي ذكرتها. لكن للأسف لا أرى تغييرًا في فلسفة وضع كل شيء في المُنشئ بحيث يمكن التحقق منه مقابل null مرة واحدة ويكون readonly ودائمًا يكون غير null خلال فترة حياة الطبقة. نحن بحاجة إلى القيام بشيء ما. لكنني لا أعتقد أن هذا سيتحسن في العام المقبل أو نحو ذلك.

اعلمني اذا كان هذا مفيدا لك.

كل هذا يساعد. سأخبرني بالفريق أنه حتى الأشخاص في العالم يعتقدون أننا بحاجة إلى التفكير في إجراء تغييرات.

ولكن إذا كنت قد تسأل هل من المعقول ل IFixture لديها Clone طريقة أو ما شابه ذلك؟ أو السماح للأشخاص بطريقة ما بإضافة التخصيص ثم إيقاف التخصيص الأخير الذي تمت إضافته بـ pop أو العودة بطريقة ما إلى الحالة قبل إضافة التخصيص؟ أوافق على أن فهم السيناريو الذي يحفز هذا السلوك المرغوب فيه أمر مهم وبالتأكيد يحتوي السيناريو على العديد من المشكلات في المقام الأول. وأرى أنك لا تريد إضافة تعقيد إضافي ما لم تكن هناك حالة استخدام جيدة. حسنًا ، يمكنك اختيار ما سيحدث بعد ذلك ، ولكن إذا كان بإمكاني المساعدة ، فأخبرني بذلك.

شكرا للقراءة والتفكير: مبتسم:

ajorians رأيي هو أنه لا يجب إضافة طريقة .Clone() إلى فئة Fixture . كما أرى ، يجب أن يكون هناك اختبار واحد فقط في أي وقت لأي اختبار واحد.

كانت هناك بعض الطلبات لتنفيذ ميزة إلغاء التجميد / الإخراج / التجميد مرة واحدة في الماضي ولكن لم يتمكن أحد من تنفيذها.
أعتقد أن السبب وراء ذلك هو عدم وجود طريقة تافهة للتعرف بشكل موثوق على المنشئ الذي سيعيد القيمة المجمدة أو ما كانت آخر قيمة تم حقنها.

ما يمكنك فعله هو تنفيذ مرحل يتجاهل الطلبات بعد عدد معين من الحلول الناجحة ، كما هو موضح أدناه. يمكنك أيضًا تحديث التنفيذ لتخطي قدر معين من الطلبات. يجب تغطية الباقي من خلال التخصيص الذي استخدمته في المنشور الأصلي.

public class CountingRelay : ISpecimenBuilder
{
    public CountingRelay(ISpecimenBuilder builder, int maxCount)
    {
        this.MaxCount = maxCount;
        this.Builder = builder;
    }

    public int Count { get; private set; }
    public int MaxCount { get; }
    public ISpecimenBuilder Builder { get; }

    public object Create(object request, ISpecimenContext context)
    {
        if (this.Count == this.MaxCount) return new NoSpecimen();
        var result = this.Builder.Create(request, context);
        if (!(result is NoSpecimen)) this.Count++;
        return result;
    }
}

مرحبًا aivascu ،

ajorians رأيي هو أنه لا يجب إضافة طريقة .Clone() إلى فئة Fixture .

نعم. حسنا شكرا لك على النظر.

ما يمكنك فعله هو تنفيذ مرحل يتجاهل الطلبات بعد عدد معين من الحلول الناجحة

سألقي نظرة.

شكرا لك مرة أخرى! : مبتسم:

ajorians لقد أنشأت طلب ميزة أكثر رسمية # 1214 لإعادة التخصيص الصريح والتلقائي. يمكنك تتبع تقدم هذه الميزة هناك.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات