أحاول اكتشاف أفضل طريقة لجعل AutoFixture ينشئ كائنًا يحتوي على قيود غير تافهة في المُنشئ. على سبيل المثال ، لنفترض أنني أريد استخدام بنية بيانات PrimeNumber التي من شأنها أن تأخذ int وتقبل فقط الأعداد الأولية.
ما هو أفضل أسلوب لإنشاء مثيل من هذا النوع من البنية في AutoFixture؟ أعني ، من الواضح أنني سأكتب تخصيصًا ، لكن ما الذي ستضعه هناك؟
علاوة على ذلك ، دعنا نقول الآن أنني أحاول إنشاء مثيل لشيء يأخذ العديد من الحجج التي يمكن نظريًا أن تكون عشوائية بشكل فردي ، ولكن هذا سيفعل بعض التحقق فيما بينها (على سبيل المثال ، يمكن أن تكون argA في هذا النطاق من القيم فقط إذا كان argB صحيح ، ويجب أن تمتثل argC لقواعد تحقق مختلفة اعتمادًا على قيمة argA ، أو يجب أن تتطابق خاصية argC.X مع خاصية argA.X ، وبعض الأشياء من هذا القبيل).
ماذا ستفعل في هذه الحالة ؟
وأخيرًا (كان بإمكاني إنشاء العديد من المشكلات ، لكنني شعرت أن كل هذه الموضوعات تمثل جانبًا مختلفًا من نفس المشكلة) ، واضطررت إلى إنشاء هذا النوع من التخصيصات وتطبيقها في كل مرة نضيف فيها فئة جديدة ، وعلينا الحفاظ على هذه التخصيصات كلما يبدو أن تغيير قواعد التحقق يتطلب الكثير من العمل ، فهل تطبق بعض الأساليب للتخفيف من ذلك؟
شكرا جزيلا ، آسف على المدى الطويل وآمل ألا تكون مشاركة فوضوية للغاية.
اعتذر لان الراديو صامت. نحن على قيد الحياة وسأرد قريبًا - أنا مشغول للغاية في عملي الأساسي هذه الأيام. تعمل أيضًا على إصدار NSubstitute v4 ، لذا فإن الوقت محدود للغاية من الموارد: متأمل: السؤال صعب ، لذا عليك التفكير في جميع الطرق الممكنة قبل نشر الإجابة.
شكرا على الصبر ، استمر في الإيقاعات: غمزة:
أهلا،
أي أخبار عن ذلك؟
لا يوجد ضغط (أعلم أن التمرين 😄 ، بالإضافة إلى أنه ليس مانعًا حقًا ، أود حقًا بعض النصائح المتعلمة) ، إنه فقط لمعرفة ما إذا كان لديك بعض الرؤية.
شكرا جزيلا!
يوم جيد! أخيرًا ، خصصت نوعًا ما للإجابة - آسف للرد المتأخر بشكل كبير
بادئ ذي بدء ، انتبه إلى أن جوهر AutoFixture بسيط جدًا وليس لدينا دعم مدمج للأشجار المعقدة ذات القيود. باختصار ، تشبه استراتيجية الإنشاء ما يلي:
باستخدام النهج الحالي ، كما لاحظت سابقًا ، لا يمكنك التحكم بطريقة أو بأخرى في قيود التبعية.
لدينا بعض نقاط التخصيص لتحديد كيفية بناء أنواع معينة ، لكنها بسيطة نسبيًا ولا تدعم تلك القواعد المعقدة.
ما هو أفضل أسلوب لإنشاء مثيل من هذا النوع من البنية في AutoFixture؟ أعني ، من الواضح أنني سأكتب تخصيصًا ، لكن ما الذي ستضعه هناك؟
هل يمكنك إنشاء ints وحلقة عشوائية حتى يصبح أحدهما عددًا أوليًا (أو تنفيذ خوارزمية توليد أولية ، بالطبع)؟ قد يكون ذلك مقبولاً لهذا النوع من القيد ، ولكن إذا كان الالتزام بالقيد أكثر صعوبة ، فسيصبح ذلك مكلفًا بسرعة.
هل ستقدم قائمة محدودة ببعض القيم المقبولة؟
حسنًا ، لسوء الحظ لا أرى حلًا سحريًا هنا ويعتمد النهج على الموقف. إذا كنت لا تعتمد على القيمة لتكون عشوائيًا جدًا ، أو أن SUT الفردي تستهلك 1-2 عددًا أوليًا فقط ، فقد يكون من الجيد ترميز الأعداد الأولية والاختيار منها (لدينا مساعد مدمج ElementsBulider<>
لتلك الحالات). من ناحية أخرى ، إذا كنت بحاجة إلى قائمة كبيرة من الأعداد الأولية وكنت تعمل باستخدام تسلسلات أعداد أولية طويلة ، فمن الأفضل أن ترميز خوارزمية لتوليدها ديناميكيًا.
علاوة على ذلك ، دعنا نقول الآن أنني أحاول إنشاء مثيل لشيء يأخذ العديد من الحجج التي يمكن نظريًا أن تكون عشوائية بشكل فردي ، ولكن هذا سيفعل بعض التحقق فيما بينها (على سبيل المثال ، يمكن أن تكون argA في هذا النطاق من القيم فقط إذا كان argB صحيح ، ويجب أن تمتثل argC لقواعد تحقق مختلفة اعتمادًا على قيمة argA ، أو يجب أن تتطابق خاصية argC.X مع خاصية argA.X ، وبعض الأشياء من هذا القبيل).
ماذا ستفعل في هذه الحالة ؟
سؤال جيد حقًا ولسوء الحظ لا يسمح AutoFixture بحله بطريقة لطيفة خارج الصندوق. عادةً ما أحاول عزل التخصيصات لكل نوع ، لذا فإن التخصيص لنوع واحد يتحكم في إنشاء نوع واحد فقط. لكن في حالاتي ، تكون الأنواع مستقلة ومن الواضح أنها لن تعمل بشكل جيد في حالتك. لا يوفر التصحيح التلقائي أيضًا سياقًا خارج الصندوق ، لذلك عندما تكتب تخصيصًا لنوع معين ، لا يمكنك فهم السياق الذي تنشئ فيه كائنًا (يسمى داخليًا العينة) بوضوح.
فوق رأسي ، أود أن أقول إنني عادةً ما أوصي بالاستراتيجية التالية:
بهذه الطريقة لن تتعارض مع العمارة الداخلية كثيرًا وسيكون من الواضح كيف تعمل. بالطبع ، من المحتمل أن تكون هذه الطريقة مطولة للغاية.
إذا لم تكن الحالات ذات القيود المعقدة شائعة ، فقد تكون الإمكانات الحالية كافية لك. ولكن إذا كان نموذج المجال الخاص بك مليئًا بالفعل بمثل هذه الحالات ، فقد لا يكون AutoFixture بصراحة هو أفضل أداة بالنسبة لك. من المحتمل أن هناك أدوات أفضل في السوق تسمح بحل مثل هذه المشاكل بطريقة أكثر أناقة. بالطبع ، من الجدير بالذكر أن AutoFixture مرن للغاية ويمكنك تجاوز كل شيء تقريبًا ، لذلك يمكنك دائمًا إنشاء DSL الخاص بك فوق نواة AutoFixture ... ولكن يجب عليك تقييم الطريقة التي تكون أرخص بالنسبة لك 😉
دعنا أيضًا نسأل ploeh عن أفكاره. عادةً ما تكون إجابات مارك عميقة ويحاول إيجاد السبب الجذري أولاً بدلاً من حل العواقب 😅
إذا كان لديك المزيد من الأسئلة، يرجى طرح! سأكون دائما موضع ترحيب للرد عليهم.
PS FWIW ، قررت أن أقدم لك عينة ، حيث حاولت اللعب باستخدام AutoFixture وحل مشكلة مماثلة (حاولت أن أبقيه بسيطًا وقد لا يعمل بالكامل في حالتك):انقر لرؤية شفرة المصدر
ج #
باستخدام النظام ؛
باستخدام AutoFixture ؛
باستخدام AutoFixture.Xunit2 ؛
باستخدام Xunit
مساحة الاسم AutoFixturePlayground
{
فئة ثابتة عامة Util
{
منطقي عام ثابت IsPrime (رقم int)
{
// تم نسخه من https://stackoverflow.com/a/15743238/2009373
if (number <= 1) return false;
if (number == 2) return true;
if (number % 2 == 0) return false;
var boundary = (int) Math.Floor(Math.Sqrt(number));
for (int i = 3; i <= boundary; i += 2)
{
if (number % i == 0) return false;
}
return true;
}
}
public class DepA
{
public int Value { get; set; }
}
public class DepB
{
public int PrimeNumber { get; }
public int AnyOtherValue { get; }
public DepB(int primeNumber, int anyOtherValue)
{
if (!Util.IsPrime(primeNumber))
throw new ArgumentOutOfRangeException(nameof(primeNumber), primeNumber, "Number is not prime.");
PrimeNumber = primeNumber;
AnyOtherValue = anyOtherValue;
}
}
public class DepC
{
public DepA DepA { get; }
public DepB DepB { get; }
public DepC(DepA depA, DepB depB)
{
if (depB.PrimeNumber < depA.Value)
throw new ArgumentException("Second should be larger than first.");
DepA = depA;
DepB = depB;
}
public int GetPrimeNumber() => DepB.PrimeNumber;
}
public class Issue1067
{
[Theory, CustomAutoData]
public void ShouldReturnPrimeNumberFromDepB(DepC sut)
{
var result = sut.GetPrimeNumber();
Assert.Equal(sut.DepB.PrimeNumber, result);
}
}
public class CustomAutoData : AutoDataAttribute
{
public CustomAutoData() : base(() =>
{
var fixture = new Fixture();
// Add prime numbers generator, returning numbers from the predefined list
fixture.Customizations.Add(new ElementsBuilder<PrimeNumber>(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41));
// Customize DepB to pass prime numbers only to ctor
fixture.Customize<DepB>(c => c.FromFactory((PrimeNumber pn, int anyNumber) => new DepB(pn, anyNumber)));
// Customize DepC, so that depA.Value is always less than depB.PrimeNumber
fixture.Customize<DepC>(c => c.FromFactory((DepA depA, DepB depB, byte diff) =>
{
depA.Value = depB.PrimeNumber - diff;
return new DepC(depA, depB);
}));
return fixture;
})
{
}
}
/// <summary>
/// A helper type to represent a prime number, so that you can resolve prime numbers
/// </summary>
public readonly struct PrimeNumber
{
public int Value { get; }
public PrimeNumber(int value)
{
Value = value;
}
public static implicit operator int(PrimeNumber prime) => prime.Value;
public static implicit operator PrimeNumber(int value) => new PrimeNumber(value);
}
}
""
مرحبا zvirja
واو ، شكرًا على الإجابة التفصيلية ، إنه أمر مثير للاهتمام حقًا. سأضطر إلى إجراء بعض الاختبارات وتقدير ما يستحق القيام به أم لا ، ولكن بشكل عام هذا رائع.
لا أعتقد أن لدي الكثير من التبعيات للتعامل معها ، لذلك قد يكون أسلوبك طريقة جيدة. بالطبع ، إذا كان لدى ploeh شيء آخر لإضافته ، فسأكون شرفًا لي 👌
شكرا مرة أخرى ، استمر في العمل الجيد!
تتمثل تجربتي مع كل من AutoFixture والاختبار المستند إلى الخاصية في وجود طريقتين أساسيتين لمعالجة مشكلات مثل هذه:
(أثناء كتابتي ، يقترح حدسي أن هذه يمكن أن تكون _Catamorphisms_ و _anamorphisms_ ، على التوالي ، ولكن علي أن أفكر في هذا أكثر ، لذا فإن هذا جانبًا هو في الغالب ملاحظة لنفسي).
إذا كانت القيم _ الأكثر_ التي تم إنشاؤها عشوائيًا تتناسب مع أي قيود يجب على المرء أن يتناسب معها ، فإن استخدام مولد موجود ، ولكن التخلص من القيمة غير المناسبة في بعض الأحيان ، قد يكون أسهل طريقة لمعالجة المشكلة.
من ناحية أخرى ، إذا كان المرشح يعني التخلص من معظم البيانات العشوائية ، فسيتعين عليك بدلاً من ذلك التوصل إلى خوارزمية ، ربما تستند إلى قيم أولية عشوائية ، ستولد قيمًا تتناسب مع القيود المعنية.
منذ بضع سنوات ، ألقيت حديثًا أظهر فيه بعض الأمثلة البسيطة لكلا النهجين في سياق FsCheck . هذا العرض هو في الواقع تطور للحديث الذي اتخذ نفس النهج ، ولكن فقط مع AutoFixture بدلاً من ذلك. لسوء الحظ ، لا يوجد تسجيل لهذا الحديث.
يمكن للمرء معالجة متطلبات العدد الأولي في كلا الاتجاهين.
سيكون أسلوب التصفية هو إنشاء أرقام غير مقيدة ، ثم التخلص من الأرقام بعيدًا حتى تحصل على رقم يمثل بالفعل عددًا أوليًا.
سيكون النهج الحسابي هو استخدام خوارزمية مثل المنخل الرئيسي لتوليد رقم أولي. هذا ليس عشوائيًا ، لذلك قد يرغب المرء في معرفة كيفية جعله عشوائيًا.
ظهر السؤال العام حول كيفية التعامل مع القيم المقيدة في AutoFixture على الفور تقريبًا بمجرد أن بدأ الأشخاص الآخرون في النظر إلى المكتبة ، وكتبت مقالًا في ذلك الوقت ما زلت أشير إليه: http://blog.ploeh.dk/2009/ 05/01 / التعامل مع المدخلات المقيدة
فيما يتعلق بمسألة القيم المتعددة التي تتعلق ببعضها البعض ، لا أرغب في إعطاء أي إرشادات عامة. هذه الأنواع من الأسئلة غالبًا ما تكون مشكلات XY. في كثير من الحالات ، بمجرد فهمي للتفاصيل ، يمكن للتصميم البديل أن يحل المشاكل ليس فقط مع AutoFixture ، ولكن أيضًا مع قاعدة رمز الإنتاج نفسها.
حتى في ظل وجود مشكلات XY ، على الرغم من ذلك ، لا تزال هناك مواقف قد يكون فيها هذا مصدر قلق مشروع ، لكنني أفضل التعامل مع هؤلاء على أساس كل حالة على حدة ، كما في تجربتي ، نادر.
لذا إذا كان لديك مثال محدد لهذا ، فقد أتمكن من المساعدة ، لكن لا أعتقد أنني أستطيع الإجابة بشكل مفيد على السؤال العام.
ploeh شكرًا جزيلاً على هذه الإجابة ، التي تؤكد المقاربات التي كنت أفكر فيها (وجعلتني أشعر بالفضول بشأن cata- و anamorphisms 😃).
أتفق تمامًا على أن القيم المترابطة هي في الغالب مشكلة XY (على الأقل في حالتي) ، والشيء هو أنه عند العمل على رمز قديم (غير مختبَر 😢) ، كان التعامل مع هذه القيم بداية جيدة لكتابة بعض الاختبارات على أي حال ، حتى نحصل على حان الوقت لإعادة بناء هذا بشكل صحيح.
على أي حال ، تعالج كلتا إجابتك المشكلة بشكل جيد ، أعتقد أنني جيد للذهاب من هناك.
شكرا!
راجع للشغل ، لقد نسيت أن أذكر أنني قصدت فقط إجابتي كإضافة إلى zvirja . إنها بالفعل إجابة جيدة هناك 👍
لم آخذه بأي طريقة أخرى 😄
التعليق الأكثر فائدة
يوم جيد! أخيرًا ، خصصت نوعًا ما للإجابة - آسف للرد المتأخر بشكل كبير
بادئ ذي بدء ، انتبه إلى أن جوهر AutoFixture بسيط جدًا وليس لدينا دعم مدمج للأشجار المعقدة ذات القيود. باختصار ، تشبه استراتيجية الإنشاء ما يلي:
باستخدام النهج الحالي ، كما لاحظت سابقًا ، لا يمكنك التحكم بطريقة أو بأخرى في قيود التبعية.
لدينا بعض نقاط التخصيص لتحديد كيفية بناء أنواع معينة ، لكنها بسيطة نسبيًا ولا تدعم تلك القواعد المعقدة.
حسنًا ، لسوء الحظ لا أرى حلًا سحريًا هنا ويعتمد النهج على الموقف. إذا كنت لا تعتمد على القيمة لتكون عشوائيًا جدًا ، أو أن SUT الفردي تستهلك 1-2 عددًا أوليًا فقط ، فقد يكون من الجيد ترميز الأعداد الأولية والاختيار منها (لدينا مساعد مدمج
ElementsBulider<>
لتلك الحالات). من ناحية أخرى ، إذا كنت بحاجة إلى قائمة كبيرة من الأعداد الأولية وكنت تعمل باستخدام تسلسلات أعداد أولية طويلة ، فمن الأفضل أن ترميز خوارزمية لتوليدها ديناميكيًا.سؤال جيد حقًا ولسوء الحظ لا يسمح AutoFixture بحله بطريقة لطيفة خارج الصندوق. عادةً ما أحاول عزل التخصيصات لكل نوع ، لذا فإن التخصيص لنوع واحد يتحكم في إنشاء نوع واحد فقط. لكن في حالاتي ، تكون الأنواع مستقلة ومن الواضح أنها لن تعمل بشكل جيد في حالتك. لا يوفر التصحيح التلقائي أيضًا سياقًا خارج الصندوق ، لذلك عندما تكتب تخصيصًا لنوع معين ، لا يمكنك فهم السياق الذي تنشئ فيه كائنًا (يسمى داخليًا العينة) بوضوح.
فوق رأسي ، أود أن أقول إنني عادةً ما أوصي بالاستراتيجية التالية:
بهذه الطريقة لن تتعارض مع العمارة الداخلية كثيرًا وسيكون من الواضح كيف تعمل. بالطبع ، من المحتمل أن تكون هذه الطريقة مطولة للغاية.
إذا لم تكن الحالات ذات القيود المعقدة شائعة ، فقد تكون الإمكانات الحالية كافية لك. ولكن إذا كان نموذج المجال الخاص بك مليئًا بالفعل بمثل هذه الحالات ، فقد لا يكون AutoFixture بصراحة هو أفضل أداة بالنسبة لك. من المحتمل أن هناك أدوات أفضل في السوق تسمح بحل مثل هذه المشاكل بطريقة أكثر أناقة. بالطبع ، من الجدير بالذكر أن AutoFixture مرن للغاية ويمكنك تجاوز كل شيء تقريبًا ، لذلك يمكنك دائمًا إنشاء DSL الخاص بك فوق نواة AutoFixture ... ولكن يجب عليك تقييم الطريقة التي تكون أرخص بالنسبة لك 😉
دعنا أيضًا نسأل ploeh عن أفكاره. عادةً ما تكون إجابات مارك عميقة ويحاول إيجاد السبب الجذري أولاً بدلاً من حل العواقب 😅
إذا كان لديك المزيد من الأسئلة، يرجى طرح! سأكون دائما موضع ترحيب للرد عليهم.
PS FWIW ، قررت أن أقدم لك عينة ، حيث حاولت اللعب باستخدام AutoFixture وحل مشكلة مماثلة (حاولت أن أبقيه بسيطًا وقد لا يعمل بالكامل في حالتك):
انقر لرؤية شفرة المصدر
ج #
باستخدام النظام ؛
باستخدام AutoFixture ؛
باستخدام AutoFixture.Xunit2 ؛
باستخدام Xunit
مساحة الاسم AutoFixturePlayground
{
فئة ثابتة عامة Util
{
منطقي عام ثابت IsPrime (رقم int)
{
// تم نسخه من https://stackoverflow.com/a/15743238/2009373
}
""