Nunit: ميزة مثيل لكل حالة اختبار

تم إنشاؤها على ٢٤ نوفمبر ٢٠١٧  ·  112تعليقات  ·  مصدر: nunit/nunit

يبدو أن النموذج العقلي للجميع هو أن هناك مثيلًا تم إنشاؤه لكل حالة اختبار عند الموازاة داخل تركيبات (https://github.com/nunit/nunit/issues/2105 ، https://github.com/nunit/nunit/issues / 2252 ، https://github.com/nunit/nunit/issues/2573) على الرغم من أن هذا لم يحدث أبدًا. أميل إلى حل هذه المشكلة دائمًا باستخدام الفئات الثابتة وتحديد فئة تركيبات متداخلة يمكن التخلص منها والتي توفر تجربة أكثر ثراءً من بعض النواحي ( مثال ) ، ولكن قد لا يكون هذا ما نريده للجميع. الجانب السلبي الطفيف هنا هو في المصطلحات ، أن الفئة الثابتة هي ما تعتبره NUnit بمثابة تركيبات ولكن الثابت الحقيقي هو الفئة المتداخلة.

إنه ليس خيارًا لجعل المثيل لكل حالة اختبارًا افتراضيًا لأن ذلك يكسر التركيبات غير المتوازية التي تعتمد على اختبار واحد قادر على الوصول إلى الحالة من اختبار آخر. من المحتمل أن يكون الأشخاص الذين يستخدمون السمة [Order] هم الأشخاص الوحيدون الذين سيؤثرون عليهم ، لكن ذلك سيؤدي إلى كسر أي اختبار يعتمد على الطلب يمكنني التفكير فيه.

يعجبني اقتراح تشارلي:

  • سمة على مستوى التجميع تسمح بتحديد الطريقة التي يتم بها إنشاء تركيبات الاختبار لتكون مثيلًا واحدًا أو مثيلًا لكل تركيبات.
  • سيعمل مثيل واحد تمامًا مثل الآن
  • سيؤدي المثيل لكل تركيبات إلى إنشاء تركيبات جديدة لكل اختبار
  • إما تنفيذ أنشطة الإعداد والتفكيك لمرة واحدة لكل اختبار (على سبيل المثال لم يعد "مرة واحدة") أو جعل OneTimeSetUp و OneTimeTearDown يتم تمييزهما على أنهما أخطاء.

الامتدادات الممكنة:

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

أود أن أقول القيام بالأساسيات أولاً ، مع ذلك.

في الوقت الحالي ، من المضمون أن يكون خطأ إذا كانت هناك حالة مثيل قابلة للتغيير وتعمل أداة التثبيت بالتوازي مع نفسها ، لذلك يمكننا الحصول على خيار تلقائي يتحول إلى مثيل لكل حالة اختبار إذا تم تكوين حالات الاختبار من المباراة للتشغيل بالتوازي وتجنب أي تغييرات تكسر . أعتقد أن Auto لديها القدرة على أن تكون مربكة ، ولكنها قد تكون أيضًا الطريقة الافتراضية الأكثر منطقية بحيث لا يضيف الجميع [assembly: InstancePerTestCase] كمسألة روتينية.

أنا أؤيد حواجز الحماية. يجب وضع علامة على OneTimeSetUp و OneTimeTearDown على أنهما أخطاء إذا كان المثبت يقوم بمثيل لكل حالة اختبار. ربما ما لم تكن ثابتة؟

done feature normal docs releasenotes

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

هل من الممكن أن نتمكن من مراجعة هذا؟ أشعر وكأنها تقترب حقًا

ال 112 كومينتر

حاليًا ، يمكنك __يمكنك__ مشاركة الحالة بين الاختبارات المتوازية المقدمة (1) يمكنك تعيين الحالة في المنشئ أو الإعداد لمرة واحدة و (2) عدم تغييرها في أي اختبار. يمكنك حتى الحصول عليها في بعض الحالات إذا تم ضبط الحالة على ثابت في الاختبار أو الإعداد ، بشرط ألا تضبطها أبدًا على أي قيمة أخرى. هذا الأخير رديء ، على الرغم من أنني أعتقد أن لدينا بعض اختبارات NUnit التي تقوم بذلك.

فكرتي هي أنه لا يمكننا تغيير الإعداد الافتراضي في أي وقت قريب ، إذا كان هناك أي وقت مضى. وجود حالة قابلة للتغيير لا يضمن وجود خطأ حاليًا. إنه خطأ فقط إذا قمت بتغييره بالفعل ولا نعرف ذلك إلا إذا قمنا بتحليل الكود - وهو ما لن نفعله!

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

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

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

ربما سمة على مستوى تركيبات التشغيل لتشغيل مثيل لكل مؤشر ترابط بدلاً من مثيل مشترك لجميع سلاسل الرسائل.

تضمين التغريدة نحن لا نفكر في أي تغيير من شأنه أن يتسبب في بدء تعطل الكود الحالي.

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

نعم ، أنا أستخدم مصطلح مؤشر الترابط ليعني التنفيذ المتوازي .... وهو أمر غير دقيق في الواقع.
اعتذاري.

من حيث XP (وآمل أن يعلم الجميع أن هذا هو وجهة نظري دائمًا) لدينا ثلاث قصص ، تبني بعضها على بعض ...

  1. يجوز للمستخدم تحديد سلوك المثيل لكل حالة اختبار عالميًا لجميع التركيبات في التجميع.

  2. يجوز للمستخدم تحديد سلوك المثيل لكل حالة اختبار على إحدى التركيبات.

  3. يمكن للمستخدمين تحديد سلوك مثيل لكل حالة اختبار للتركيبات التي تحتوي على اختبارات قد تعمل بالتوازي.

أود أن أرى (1) تم تنفيذه بالكامل في هذه المشكلة. أعتقد أنه يجب علينا فقط إخراج (2) من رؤوسنا - أو على الأقل الخروج من الجزء الذي يكتب الكود - في الوقت الحالي. أعتقد أن (3) هو احتمال ولكن لديه بعض المزالق التي واجهناها بالفعل في قضايا أخرى. (لا يعرف الجهاز ما إذا كان سيتم تشغيل حالات اختبار بالتوازي!) دعنا نتحدث عن ذلك JustInTime ، والذي سيكون بعد اكتمال ودمج (1) و (2). FWIW ، أعتقد أن لدينا ميلًا للتقييد من خلال محاولة التفكير في عدة خطوات للأمام. لقد ذكرت (2) و (3) حتى لا نحظرهم بأي شكل من الأشكال ، والذي يبدو أنه من السهل تجنبه.

أعتقد أننا جميعًا متفقون في ذلك الوقت. لدي بعض أولويات NUnit قبل ذلك ، لذا سأتركها للآخرين للتعليق عليها وربما التقاطها.

جمال القيام بذلك باستخدام سمة بدلاً من إعداد سطر أوامر هو أنه سيعمل مع أي عداء.

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

هل ترغب في معرفة ما إذا كانت هناك أي خطط وشيكة لتنفيذ هذه الميزة؟

rprouse لا يزال هذا يحمل علامة على أنه يتطلب تصميمًا ولكن يبدو كما لو أن المناقشة غطت كل شيء بالفعل.

وافق CharliePoole على أن هذا يمكن أن ينتقل من التصميم. pfluegs لم نبدأ العمل على هذا بعد تصميمه ، لذلك لا توجد خطط بعد.

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

لقد تعرضت للتو للعض بشدة بسبب هذه المشكلة بالذات - +1 للإصلاح.

في حال أردنا المزيد من الخيارات ، هل نريد:

ج #
[التجميع: FixtureCreation (FixtureCreationOptions.PerTestCase)]

And:
```diff
namespace NUnit.Framework
{
+   public enum FixtureCreationOptions
+   {
+       Singleton, // = default
+       PerTestCase
+   }
}

لقد تغير تفكيري الخاص حول هذا قليلاً ، في سياق إطار عمل جديد على الأقل.

أعتقد الآن أنه يجب أن يكون هناك سمتان على مستوى الفصل ، واحدة لكل سلوك. سأجعل TestFixture يعمل مع مثيل لكل اختبار ويعمل Shared Fixture مثل NUnit الآن. من الواضح أن هذا تغيير جذري في سياق NUnit ، ولكن قد يكون النهج العام يستحق الدراسة.

CharliePoole [TestFixture(FixtureOptions.PerTestCase)] يبدو وكأنه بناء جملة جميل ، لكنني أرغب بشدة في الحصول على سمة على مستوى التجميع أيضًا حتى أتمكن من الاشتراك في كل مشروع اختبار. كيف نريد أن تبدو سمة على مستوى التجميع؟

حسنًا ، لهذا السبب عبرت عن تحفظي WRT عما كنت سأفعله في إطار عمل جديد. ومع ذلك ، في حالة NUnit ، لن ترغب في تغيير دلالات TestFixture ولا يمكنني التفكير في اسم جيد لسلوك المثيل لكل حالة. إذا كان لديك اسمان ، فكل ما عليك فعله للحصول على السلوك المطلوب في جميع المباريات هو إجراء تعديل شامل لملف المصدر الخاص بك.

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

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

@ nunit / framework-team ما رأيك؟ للإدخال الأولي للميزة:

  • نعم / لا لإعداد مستوى التجميع؟
  • نعم / لا لتحديد مستوى المباراة؟
  • نعم / لا لتحديد مستوى الأسلوب؟
  • نعم / لا لاختبار الإعداد على مستوى الحالة؟

نقطة جيدة حول فئات التركيبات عديمة النفع.

سأقول نعم ، نعم ، لا ، لا

أتفق مع تشارلي - مع التوضيح بأن "مستوى المباراة" يعني "مستوى الفصل". (على عكس طريقة مصدر حالة الاختبار على سبيل المثال. 😊)

هذا يساعد! لذا فإن المشكلة مع [TestFixture(FixtureCreationOptions.PerTestCase)] هي أن سمات TestFixture المتعددة مسموح بها في نفس الفئة ومن ثم يمكن أن تتعارض مع بعضها البعض. من الأفضل والأبسط أن يكون لديك سمة مسماة جديدة تنطبق على التجميعات والفئات؟

ج #
[التجميع: ShareFixtureInstances (خطأ)]

[ShareFixtureInstances (خطأ)]
فئة عامة SomeFixture
{
// ...
}

Framework API:

```diff
namespace NUnit.Framework
{
+   [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)]
+   public sealed class ShareFixtureInstancesAttribute : PropertyAttribute
+   {
+       public bool ShareFixtureInstances { get; }

+       public ShareFixtureInstancesAttribute(bool shareFixtureInstances);
+   }
}

يبدو جيدا. ما هو سلوك الوراثة لهذا - إذا كانت لدي السمة في فئة أساسية ، مع اختباراتها الخاصة ، ثم ورثت أيضًا من تلك الفئة؟ 😊

من المفترض ، نظرًا لأنها سمة "فئة" - يجب أن تكون في الفصل الفعلي الذي يتم تشغيل الاختبارات المعنية منه؟

أيضًا ، أنا لست حريصًا تمامًا على الاسم - نظرًا لأن التركيب ليس دائمًا فئة في مصطلحات nunit ، لا أعتقد (على سبيل المثال ، جميع الاختبارات في مصدر الاختبار - ما لم يكن لدى `` جناح '' و `` تركيبات '' دلالات أقوى معنى مما أعتقد ، في مصطلحات نونيت!)

ماذا عن شيء على غرار InstancePerTest ... ولكن أفضل تسمية ...

متفق عليه حول مستوى التجمع والطبقة ولكن ليس أعمق. أفضل أيضًا شيئًا على غرار اقتراح ChrisMaddock ClassInstancePerTest(bool) . أفترض أيضًا أن السمة على مستوى الفصل ستتجاوز مستوى التجميع وأتوقع أن تكون موروثة. أعتقد أن الوراثة مهمة لأنك تريد عادةً مثيلًا لكل اختبار بناءً على أعضاء الفصل جنبًا إلى جنب مع التوازي. لذلك قد ترغب في تطبيق ذلك تلقائيًا على الفئات المشتقة.

فوجئت مرة واحدة بصدق NUnit لا إنشاء مثيل في الاختبار عندما نوع اختبار اعبا اساسيا هو constructible. لطالما فكرت في اختبارات الوحدة ، مثل ، لا أعرف ، اختبار الوحدة : قمت بإعداد السقالات ، وقمت بإجراء الاختبار ، وذهبت السقالات. لا نقل غير مقصود للحالة بين الاختبارات.

بعد أن تعلمت ذلك. أنا ، مثل @ jnm2 ، [TearDown] بالنسبة لك بعد الآن. :( لا توجد طريقة متسقة لتمييز أخطاء الاختبار عن أخطاء السقالات أيضًا ، فهذه حالة استخدام مؤلمة حقًا.

إذا كان بإمكاني الدخول في التصميم ، فأنا مع CharliePoole على نعم ، نعم ، لا ، لا.

الاسم PerTestInstance ، InstancePerTest أو شيء مشابه لذلك يبدو أفضل بكثير من ShareFixtureInstances . أنا لا أحب كلمة Class في اسم السمة ، لأنه عندما أقوم باختبار كود F # ، لا أفكر في مثيلات النوع التي تكون فئات . لكن الشيء المؤكد يمكنني التعايش مع ذلك! :)

يا فتى ، لا يمكن أن تنتظر هذه الميزة!

@ kkm000 أوافق على استخدام "class". تستخدم NUnit عادةً أسماء مختلفة عن التنفيذ. حتى مع ذلك ، يضع الناس افتراضات ، لكن من الأفضل أن تكون متسقًا في تجنب مثل هذه الأشياء.

أمثلة على ما أتحدث عنه: Test ، TestFixture ، SetUpFixture ، العامل (وليس الخيط) ، إلخ. بشكل أساسي ، يمثل الفصل __could__ اختبارًا واحدًا ، وليس تركيبات ... لا أقول إننا نقوم بذلك أو حتى يجب ، ولكن __يمكننا _.

@ kkm000

شكرا على التعليق! أنت لا تتدخل على الإطلاق. هدفنا التصميمي هو أن نكون حياديين للغة حيثما أمكن ذلك.
إلى جانب التركيز على C # ، هناك شيء آخر يتعلق بكلمة class : ماذا لو قمنا يومًا ما بدعم التركيبات التي ليست فئات؟

[InstancePerTestCase] سيتجنب المشكلة ، لكنه قد يفقد الوضوح البديهي.

لا تتباعد التركيبات والفئات كثيرًا ، ولكن إذا تباعدوا ، فأنا متأكد تمامًا من أن هذه السمة تتعلق بالتركيبات بدلاً من الفئات.

تعتبر الأجنحة مفهومًا تنظيميًا عامًا ، في حين أن التركيبات هي الأشياء التي تحمل منطق الإعداد / التمزيق وحالة الاختبار ويتم تمريرها إلى طرق الاختبار (حاليًا كطرق المثال ' this وسيطة). لا يجب أن تكون التركيبات جزءًا من التسلسل الهرمي التنظيمي. دعنا نتخيل تركيبات تختلف عن مفهوم الفصول وتختلف عن مفهوم مجموعات الاختبار:

ج #
فئة ثابتة عامة SomeTestSuite // ليست تركيبات: لا يوجد منطق إعداد / تفكيك ولا حالة اختبار
{
[اختبار] // اختبار فردي ، وليس مجموعة معلمات ؛
// fixture يتم إدخاله عبر معامل بدلاً من هذا المعامل الضمني
باطل ثابت عام SomeTest (تركيبات SomeFixture)
{
fixture.SomeService.SomeProperty = صحيح ؛
fixture.SomeAction () ؛
fixture.AssertSomething () ؛
}
}

[تثبيت إختبار]
الهيكل العام SomeFixture: IDisposable
{
SomeService SomeService العامة {get؛ } // ولاية

public SomeFixture() // This was almost legal syntax in C# 6, but I'm making a point 😁
{
    // Setup
}

public void Dispose()
{
    // Teardown
}

public void SomeAction()
{
    // Helper method
}

public void AssertSomething()
{
    // Helper method
}

}
""

في المثال أعلاه ، لا يركز [FixtureInstancePerTestCase] أو [SharedFixtureInstances(false)] على الفصول ولا يركز على التسلسل الهرمي (الأجنحة): إنه يركز على التركيبات ، والتركيبات هي منطق وحالة الإعداد / التمزيق القابل لإعادة الاستخدام. على وجه التحديد ، يركز على ما إذا كان قد تم إنشاء SomeFixture لكل حالة اختبار أو ما إذا كان يتم تمرير نفس الحالة لكل اختبار.

منذ هذه السمة سيسيطر سواء ITests مختلفة وتبادل حالات ITest.Fixture، والشيء الذي تسيطر هي كل ما تزيين مع [TestFixture] (سمة أخرى مع Fixture في الاسم)، يبدو وكأنه _consistent_ على استخدام كلمة Fixture.

(أقوم بإحضار النقاط التي يبدو من المهم مناقشتها ، لكنني لست مستثمرًا بشكل كبير فيما ينتهي به الاسم.)

@ jnm2 أنت محق تمامًا في أن مفهوم الجناح

على حد علمي ، كان xunit.net أول إطار عمل (فقط؟) لجعل لاعبا أساسيا كيانا منفصلا عن اختبار التسلسل الهرمي للميراث.

عندما أنشأت اختبارات ذات معلمات ، كان بإمكاني تقديم TestCaseSource كـ "تركيبات" منفصلة. لم أفعل ذلك وأعتقد أن التغييرات اللازمة لإعادته ستتعطل الآن.

أعتقد أن السبب الذي يجعل الناس يسألون أحيانًا (قبل التوازي) عن سلوك كل حالة هو أن هذا ما تفعله الأطر الأخرى. لسبب مشابه __لا _ يطلبوا تركيبات قابلة للفصل.

لذا ، تلخيصًا ، هذا ما أعتقده ...

  1. نحتاج إلى مثيل لكل حالة اختبار.

  2. لا يمكن أن يكون الإعداد الافتراضي بسبب التوافق مع الإصدارات السابقة.

  3. تعتبر التركيبات القابلة للفصل فكرة رائعة ، ولكنها أيضًا مشكلة منفصلة.

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

فقط للتأكيد على النقطتين 3 و 4 ، لم أقصد نموذج الكود الخاص بي كاقتراح لميزات جديدة ولكن كتجربة فكرية تهدف إلى مساعدتنا في الوصول إلى الاسم الصحيح لهذه السمة. تخبرني التجربة الفكرية أننا نركز حقًا على فكرة التثبيت ، وليس فئة أو مجموعة ، على الرغم من أنها تتزامن الآن.

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

هل هذا يبدو صحيحا للجميع؟ إذا لم يكن كذلك ، فما هي النقاط الأخرى التي يمكننا النظر فيها؟

آه! آسف ، اعتقدت أنك وصلت إلى نقطة جيدة جدًا ، وعادة ما يتم تجاهلها ، لكنني كنت خائفًا أيضًا من أننا قد نكون استطراديين. : قليلا_التجعد_الوجه:

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

نعم ، نقطة جيدة.

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

دعنا نحصل على بعض الخيارات ويمكننا التعديل من هناك.
نحن نبحث عن شيء يوازن بين الحدس والاتساق مع أسلوب NUnit والقدر المناسب من المرونة. (بالنسبة للجزء الأكبر ، يجب أن تتداخل هذه الأشياء بالفعل. 😄)


الاقتراح أ

ج #
[التجميع: FixtureOptions (InstancePerTestCase = true)]

[FixtureOptions (InstancePerTestCase = false)]
فئة SomeFixture
{
}

Pro: extending later if there's ever more options for fixture handling (doubtful?)
Pro: two names so there's less stuffed into a single name
Con: `[FixtureOptions]` would be legal but highly misunderstandable syntax. (Does it reset the assembly-wide setting to the default, `false`, or does it have no effect, e.g. `null`?)


### Proposal B

```c#
[assembly: FixtureOptions(FixtureCreationOptions.PerTestCase)]

[FixtureOptions(FixtureCreationOptions.Singleton)]
class SomeFixture
{
}

public enum FixtureCreationOptions
{
    Singleton, // = default
    PerTestCase,
    // Doubtful there will be other options?
    // Maybe Pool, where we reset and reuse between nonconcurrent tests?    
}

المؤيد: الحالات غير الصالحة غير قابلة للتمثيل
المؤيد: يتم التمديد لاحقًا إذا كان هناك المزيد من الخيارات للتعامل مع التركيبات (مشكوك فيها؟)
Con: التكرار في كتابة كل من اسم السمة واسم التعداد

الاقتراح ج

ج #
[التجمع: FixtureInstancePerTestCase]

[FixtureInstancePerTestCase (خطأ)]
فئة SomeFixture
{
}
""

المؤيد: الحالات غير الصالحة غير قابلة للتمثيل
المؤيد: من الصعب إساءة فهمها (لكن لدي نقطة عمياء هنا لأنني لست مستخدمًا جديدًا)
يخدع: اسم طويل
ربما يخدع: اسم محدد جدًا يمكنه تحديد الخيار والقيمة. (ولكن لدينا أيضًا [NonParallelizable] الذي يعجبني.)


(نرحب بالمقترحات البديلة!)

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

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

اقترب من استخدام كل بديل من البدائل الخاصة بك:

ج #
[التجميع: DefaultFixtureOptions (InstancePerTestCase = true)]

[FixtureOptions (InstancePerTestCase = false)]
فئة SomeFixture
{
}

[التجميع: DefaultFixtureOptions (FixtureCreationOptions.PerTestCase)]

[FixtureOptions (FixtureCreationOptions.Singleton)]
فئة SomeFixture
{
}

[التجمع: DefaultFixtureInstancePerTestCase]

[FixtureInstancePerTestCase (خطأ)]
فئة SomeFixture
{
}
""

تعليقات أخرى:

  • أنا أحب "أ" و "ب" أفضل من "ج"
  • لا يجب أن تسمي سلوك التركيبات المشتركة "Singleton" ، لأنه ليس كذلك. : ابتسامة: إذا ورثت ، ستحصل على مثيلات متعددة من التركيبات الأساسية.
  • إذا كنت تستخدم سمات منفصلة على مستوى التجميع والتثبيت ، فيمكنك في الواقع إسقاط تركيبات من الاسم على مستوى المباراة.
  • لم نحدد كيف تتناسب SetUpFixture مع هذا على الإطلاق. إنها إشكالية وقد تحتاج إلى خطأ في وقت التشغيل إذا تم استخدام السمة عليها. في الواقع ، تتمثل إحدى ميزات السمة المنفصلة تمامًا في أننا لسنا مضطرين للتعامل مع هذا الأمر. (إنها أيضًا ميزة لخاصية TestFixture ، راجع للشغل)
  • نحتاج إلى تحذير الأشخاص من أن حالة المثيل لكل اختبار لا تنطبق على الفئات الثابتة d. إنه واضح لنا جميعًا ولكن هناك أشخاص لن يفهموه على الفور.

راجع للشغل ، لقد تمنيت في كثير من الأحيان أن نذهب مع DefaultTestCaseTimeOut من أجل مستويات التجميع والتثبيت والاحتفاظ بـ Timeout إلى الأساليب فقط.

جيد ، شكرا لطرح هذا الاحتمال.

هناك شيء يزعجني الآن هو أن هذا قد يكون منطقيًا من الناحية النظرية ، على الرغم من أننا لا نسير في هذا الاتجاه:

c# [DefaultFixtureOptions(InstancePerTestCase = true)] class SomeFixture { [FixtureOptions(InstancePerTestCase = false)] public void SomeTest() { } }

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

أو:

ج #
[التجميع: DefaultFixtureOptions (InstancePerTestCase = true)]

// لماذا لا يتم اعتبار ذلك افتراضيًا أيضًا؟
[DefaultFixtureOptions (InstancePerTestCase = true)]
فئة المجردة العامة BaseFixture
{
}

فئة مختومة عامة SomeFixture: BaseFixture
{
}
""

كيف نتوصل إلى سبب منطقي لمتى ومتى لا تستخدم Default ؟

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

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

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

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

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

تركيبات إعداد WRT ، راجع للشغل ، أعتقد أنه يجب إنشاء خطأ في وقت التشغيل لتحديد أي خيار إنشاء مثيل هناك. SetUpFixture ليست TestFixctions.

أي أن وضع السمة A على الفئة الأساسية و B على الفئة المشتقة لا يختلف عن وضع A و B مباشرة على الفئة الأساسية. لقد صممناه بهذه الطريقة لأنه سيكون من المفاجئ للمستخدم أن يفعل أي شيء آخر.

حسنًا ، كنت أتوقع أن يقوم [Apartment] أو [Parallelizable] باستبدال إعداد الفئة الأساسية أو الطريقة الأساسية!

كل هذا منطقي بالنسبة لي.

هل يمكننا استبعاد الحاجة إلى تعداد مثل FixtureCreationOptions ؟ PerTestCase ، PerFixture ، افتراضي ReusedPerTestCase ؟

أنا أميل إلى بساطة:

ج #
[التجميع: DefaultFixtureOptions (InstancePerTestCase = true)]

[FixtureOptions (InstancePerTestCase = false)]
فئة SomeFixture
{
}

Hypothetical other creation options would then be added this way:
```c#
[FixtureOptions(InstancePerTestCase = false, ReuseInstances = true)]

@ nunit / framework-team ما هي تفضيلاتك؟

بخصوص

[assembly: DefaultFixtureOptions(InstancePerTestCase = true)]

ربما أنا فقط ، لكن بشكل عام أفهم السمات على أنها تشير إلى أن الكائن المميز يمتلك سمة معينة ، غير موجودة بطريقة أخرى ؛ حجج السمة ، إن وجدت ، تنقح السمة المذكورة. خذ ، على سبيل المثال ، Serializable - الكائن قابل للتسلسل ، ولكن إذا كان غير مزخرف ، فهو ليس كذلك ؛ InternalsVisibleTo - الداخلية مرئية لشيء محدد ، والحجج تنقح هذا ، وإلا فإن الداخلية غير مرئية لأي شيء ، وما إلى ذلك. بعض السمات لا تتبع النمط ، ولكنها عادة ما تكون منخفضة المستوى ، e ، g ، CompilationRepresentation ، MethodImpl. هذه عادة ما تأتي مع الحجج المنشئة المطلوبة.

فكر الآن
c# [assembly: DefaultFixtureOptions]
هذه لغة C # جيدة التكوين ، نظرًا لأن التعيين إلى InstancePerTestCase اختياري. ما الذي يعبر عنه دلالة؟

بالنسبة لي ، يبدو [assembly: DefaultFixtureInstancePerTestCase] أكثر طبيعية (مع استثناء لاسمه sesquipedalian ، لكن هذا مجرد رأي ، وليس حفظًا جماليًا مبررًا). صفة واحدة ، سمة واحدة. ربما لا يحتاج هذا إلى أي معلمات أو خصائص قابلة للتعيين اختياريًا.

أيضًا ، كمستخدم ، أجد أنه من المدهش وجود سمتين بأسماء مختلفة ، واحدة قابلة للتطبيق فقط على مستوى التجميع ( DefaultSomething ) والأخرى فقط على مستوى النوع ( Something ) . بطبيعة الحال ، لا أفهم ما هو "الدرس المستفاد" الذي ذكره CharliePoole بخصوص السمة Parallelizable ، ولكن من هذا الجانب من الجزيرة ، فإن زوجًا من السمات لتعلم أين يمكن للمرء أن يفعل (في التفكير السطحي لهذا المستخدم) يبدو محيرًا.

@ kkm000 أتفق مع تعليقك حول سمة تعبر عن سمة واحدة.

WRT سمتان مختلفتان للسمة "نفسها" على مستويات مختلفة ، أنا __سأوافق إذا اعتقدت أنهما متماثلان بالفعل. لكنني لا أعتقد أنهم كذلك. سأستخدم Timeout كمثال.

إذا تم وضع Timeout على طريقة اختبار ، فهذا يعني أن "طريقة الاختبار هذه ستنتهي إذا استغرق تشغيلها أكثر من عدد المللي ثانية المحدد." ومع ذلك ، عند وضع Timeout على تركيبات أو تجميع ، يكون له معنى مختلف: "المهلة الافتراضية لجميع حالات الاختبار الواردة في هذا العنصر هي بالعدد المحدد من المللي ثانية." يمكن بالطبع تجاوز هذا التقصير في المستويات الأدنى. معنيان مختلفان إلى حد ما ، وبالمناسبة لهما تطبيقان منفصلان. الأول يطبق المهلة على الاختبار الذي يظهر فيه. يخزن الثاني الإعداد الافتراضي في سياق الاختبار حيث سيتم العثور عليه واستخدامه كإعداد افتراضي عند مواجهة حالات الاختبار المضمنة. ParallelizableAttribute يعمل بالمثل.

في هذه الحالة بالذات ، عند وضعها على إحدى التركيبات ، تعني السمة "إنشاء هذه الوحدة مرة واحدة لكل حالة اختبار". على مستوى التجميع ، هذا يعني "إنشاء تركيبات اختبار في هذا التجميع مرة واحدة لكل تركيبات ما لم يتم تحديد خلاف ذلك."

في هذه الحالة بالذات ، عند وضعها على إحدى التركيبات ، تعني السمة "إنشاء هذه الوحدة مرة واحدة لكل حالة اختبار". على مستوى التجميع ، هذا يعني "إنشاء تركيبات اختبار في هذا التجميع مرة واحدة لكل تركيبات ما لم يتم تحديد خلاف ذلك."

بافتراض أنك أخطأت في الكتابة في الجملة الأخيرة - هل كان _ "إنشاء تركيبات اختبار في هذا التجميع مرة واحدة لكل حالة اختبار ~ تركيبات ما لم يتم تحديد خلاف ذلك." _ المقصود؟ أم أنني أسيء فهم النطاق والهدف بشكل خطير؟

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

اثنين من تطبيقات منفصلة

في عالم gedankenworld من أحادي القرن وأقواس قزح ، لا يؤثر التنفيذ على الجانب المواجه للمستخدم. لكننا (أنت مصمم NUnit وأنا أحد مستخدميها) جميعًا مهندسون ، والهندسة تدور حول الحلول الوسط. إذا كان استخدام سمة واحدة لنطاقات مختلفة سيؤدي في الواقع إلى klugity الداخلي وعبء الصيانة ، فلن أتعثر على الإطلاق. :)

في النهاية ، هذه مسألة ثانوية بالنسبة لي ، ولا أشعر بقوة بنفس السمة ويمكنني التعايش مع السمات مرتبة في كلتا الحالتين.

@ kkm000 نعم ، أنوي "مرة واحدة لكل حالة اختبار."

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

كمثال على الأخير ، ضع في اعتبارك الفئات المتداخلة والفئات الأساسية. لم يتم التعامل مع أيٍّ منهما من قبل NUnit على أنهما "نطاقات" متداخلة لأغراض الاختبار ، لكنهما قد يبدوان بهذه الطريقة للمستخدمين.

سأوافق على أن المشكلة التي ذكرتها مع Timeout / DefaultTimeout أكثر جدية بعض الشيء ، لأن Timeout على أحد التركيبات قد يعني بسهولة أن المهلة تنطبق على المباراة بأكملها ، وهو ما لا يحدث بالطبع. لهذه المسألة ، قد تنطبق مهلة التجميع على التجميع بأكمله ، وهو ما لا ينطبق.

في هذه الحالة ، يبدو أن هناك قدرًا أقل من الغموض ، لأننا نستخدم "لكل حالة اختبار" في الاسم حتى يمكنني الانتقال بسهولة في أي من الاتجاهين. أنا لا أعمل على ذلك على أي حال. :ابتسامة:

تعال إلى التفكير في الأمر ، إذا تم استدعاء Timeout TestCaseTimeout لكان ذلك أفضل!

أنا سعيد بـ [FixtureInstancePerTestCase] . يبدو أقل عبء عقلي على المستخدمين.

@ nunit / framework-team هل يمكنك مساعدتنا في الاستقرار في اتجاه ما ، من خلال الانتقال من https://github.com/nunit/nunit/issues/2574#issuecomment -426347735 لأسفل؟

[FixtureInstancePerTestCase] هو أيضًا الخيار الأقل كرهًا لدي. الافتقار إلى التمدد هو الجانب السلبي الوحيد.

لا أحب مشكلة [FixtureOptions] - أعتقد أن هذا محير للغاية. أنا شخصياً أفضل سمة واحدة لكل من التجميع والفصل - أقدر أن هذا تسبب لنا في حدوث مشكلات من قبل لـ Timeout و Paralellizable - ولكن عندما يكون لهذه الخاصية "Fixture" في الاسم ، فإنها تبدو أقل غموضًا بالنسبة لي.

أنا شخصياً أقتبس إعجابي بخيار التعداد ... إلا أنني لا أستطيع التفكير في كيفية تسميته بشكل مناسب لجعله أكثر إيجازًا!

هل هناك أي تحديث على ذلك؟

سيتم نشر أي تحديثات لهذه القضية.

يمكن لأي شخص حل هذه المشكلة من فضلك ؟؟
في الوقت الحالي ، يجب علينا تغليف جميع أكواد الاختبار الخاصة بنا على النحو التالي:
csharp [Test] public void TestExample(){ using(var tc = new MyTestContext()){ //code goes here } }
باستخدام هذه الميزة ، يمكننا تهيئة سياق الاختبار عند الإعداد والتخلص منه عند التمزيق مع استمرار كونه آمنًا.

مرحبًا ، نحن نواجه هذه المشكلة في شركتنا أيضًا. نحن نرحل من إطار عمل يدعم هذه الميزة (Py.Test) ، لذلك هذا قليل من مسكتك. سنكون ممتنين لو عرضت لنا الكود المصدري الذي يتحكم في هذا التماثل حتى نتمكن من تفرعها وإجراء التغييرات اللازمة.

تضمين التغريدة
إذا لم يتم إصلاح هذه المشكلة قريبًا ، فإننا نفكر في الترحيل إلى xUnit ، نظرًا لأن هذا هو السلوك الافتراضي في xUnit: "ينشئ xUnit.net مثيلًا جديدًا لفئة الاختبار لكل اختبار يتم تشغيله" https: // xunit .github.io / مستندات / سياق مشترك

LirazShay ، MChiciak لقد واجهت هذه المشكلة أيضًا لكنني أتفق مع نقاط CharliePoole . لذلك قمت بعمل الحقول الخاصة بالاختبار والتي تمت تهيئتها في SetUp [ThreadStatic] وهي تعمل بشكل جيد.

dfal
شكرا على الحل الجميل!

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

كتجربة فكرية ، تخيل أننا ببساطة غيرنا طريقة عمل TestFixture. ما من شأنه كسر هذا؟

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

  2. اعتمادًا على ما يتم إجراؤه في الإعداد لمرة واحدة ، من المحتمل ألا تتمكن مثيلات التثبيت الفردية من العمل بالتوازي. هذا مثير للسخرية ، لأن الدافع وراء التغيير هو السماح للتركيبات بالعمل بالتوازي! لا يعد هذا تغييرًا مفاجئًا للأشخاص الذين يحتاجون إلى الميزة ، نظرًا لأنهم غير قادرين بالفعل على العمل بشكل متوازٍ ، ولكنه تغيير فاصل كبير لأولئك الذين يعتمدون بالفعل على إجراء الاختبارات بالتوازي __بعد__ وهو OneTimeSetUp غير قابل للتوازي.

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

لقد وجدت هذه التجربة الفكرية مفيدة في التفكير في كيفية تنفيذ إنشاء مثيل لكل حالة اختبار للتركيبات. من الواضح أنه يجب أن تكون ميزة جديدة غير افتراضية إذا تم تقديمها في NUnit 3. بالنسبة إلى NUnit 4 أو أي إطار عمل جديد آخر (مثل TestCentric) ، يمكن أن يكون السلوك الجديد هو الافتراضي بسهولة.

كملاحظة ، لقد قمت ببساطة باستبدال بعض MSTest TestClass و TestMethod بـ TestFixture و Test على التوالي ، واكتشفت أن NUnit و MSTest لهما دلالات مختلفة هنا. ينشئ MSTest مثيلات جديدة لكل اختبار حيث ، كما أنا متأكد من أن كل من يقرأ هذا يعلم ، تعيد NUnit استخدام مثيلات TestFixture.

سيكون من الجيد لو كان بإمكاني كتابة TestFixture(lifeCycle = OneTestPerInstance)] أو ما شابه ، لتسهيل هذا الترحيل.

_edit: _ في الواقع ، بعد نقل الاختبارات لاستخدام طريقة Setup ، اتضح في الواقع أن مُهيئ الكود الحالي / قبل / teardown تم تعذيبه إلى حد ما تحت MSTest ، وهو مُنشئ خاصية بسيط الآن ، والذي أجمل بكثير وأكثر اكتمالًا ، لذلك أنا سعيد لأنني أجريت الترجمة. ومع ذلك ، كمسألة التوافق ، سيكون من الجيد أن يكون لديك.

تضمين التغريدة

أي اختبارات تستخدم الترتيب وتعتمد على تغييرات الحالة التي يتم إجراؤها بواسطة اختبار واحد للتأثير على الاختبارات الأخرى.

آه ، نعم ، كسر ، كسر هذه الاختبارات ، من فضلك جميلة !!! 🤣 هذا هو أسوأ استغلال لإطار عمل اختبار الوحدة الذي يمكنني التفكير فيه.

أي مستوى لاعبا اساسيا OneTimeSetUp

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

لكن نعم ، الأداء هو أيضًا نقطة صالحة جدًا.

هذه الميزة قيد المناقشات منذ أكثر من عامين حتى الآن ، ولا أقصد أن أبدو مسيئة ، ولكن هل هناك أي خطط ملموسة وواقعية لتنفيذها فعليًا في المستقبل القريب؟

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

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

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

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

على سبيل المثال ، عندما صادفت لأول مرة حقيقة أن NUnit TestFixture عبارة عن مفردة قمت بتحويل تركيبات الاختبار الخاصة بي 100+ مع أكثر من 500 اختبار لتكون خيطًا آمنًا (واضطررت إلى الانتقال من RhinoMocks إلى Moq لأن RhinoMocks ليس أيضًا آمنًا للخيط) وقد استغرق ذلك أنا حوالي 4 أسابيع (لأكون صادقًا ، Rhino -> استغرق Moq معظم ذلك الوقت!)

أنت محق بنسبة 100 ٪ من حيث المبدأ ، ولكن عندما يكون لديك مجموعة اختبار حالية من حوالي 7000 + اختبارات وحدة ، فإن مجرد إعادة كتابتها لتكون آمنة في الخيط يعد استثمارًا ضخمًا.

لست متأكدًا مما إذا كان استثمارًا أكثر من الانتقال إلى إطار عمل آخر (قد يكون هناك بعض الأدوات لأتمتة ذلك) لكنني لا أعتقد أنه يجب تجاهل طلب الميزة هذا من حيث المبدأ ، IMO

أنا لا أدعو إلى عدم النظر إلى هذه الميزة.

لكن مثل هذا التغيير الكبير في طريقة عمل nunit ، في رأيي يجب أن يؤخذ في الاعتبار إما للتوافق مع الإصدارات السابقة (والذي قد يكون صعبًا للغاية) أو إصدار رئيسي جديد (أيضًا ليس مهمة صغيرة)

أقترح أن جعل الكود مستخدمًا لـ nunit يكتب Threadafe للتشغيل في بيئة متعددة الخيوط هو شكل أسهل من أشكال العمل. يمكنك حتى كتابة ذلك.

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

تناقش هذه المسألة برمتها طرقًا مختلفة يمكن من خلالها تنفيذ مفتاح اختياري ... هناك العديد من الطرق

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

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

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

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

BlythMeister أعتقد أن التبديل يفعلون ذلك.

بالتأكيد ، إنه مفتاح ، لكنه لا يقتصر على تشغيل المصباح الكهربائي. إنه أشبه بتبديل الراديو من AM إلى FM ... تدخل دوائر جديدة تمامًا.

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

آسف على حقك ، لقد كان توضيحي غامضًا لأنهم لم يكن "التبديل" بسيطًا!

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

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

مع NUnit 3 ، كنا نأمل أن نرى المزيد من المستخدمين يفحصون النتائج خارج إطار العمل باستخدام امتدادات المحرك ولكن يبدو أن العمل داخل الاختبار نفسه أكثر دراية ويمكن الوصول إليه.

تضمين التغريدة

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

لماذا لا يمكننا تنفيذ ميزة Instance-per-test-case وما زلنا نشغل OneTimeSetUp مرة واحدة فقط (احتفظ بسلسلة المحادثات الرئيسية تذكر استدعاءها أو شيء من هذا القبيل)؟

لأنه سيعطل أي OneTimeSetUp موجود قام بتهيئة المثيل ، وهو أمر شائع جدًا.

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

لأنه سيعطل أي OneTimeSetUp موجود قام بتهيئة المثيل ، وهو أمر شائع جدًا.

ماذا عن السماح فقط بالطرق الثابتة OneTimeSetUp و OneTimeTearDown عند تمكين ميزة "مثيل لكل حالة اختبار"؟ سيؤدي ذلك إلى إجبار الأعضاء الذين تمت تهيئتهم ضمن هذه الأساليب على أن يكونوا ساكنين ، وبالتالي من الواضح أنه يتم مشاركتهم بين حالات التثبيت.

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

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

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

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

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

@ fschmied راجع للشغل ، إذا كنا نقوم بعمل إطار عمل جديد (أو ربما حتى NUnit 4)

أفضل استبدال TestFixture بنوع جديد تمامًا من التركيبات ، والذي يعمل بشكل مختلف.
[...]
أعتقد أنني المدير الأقل مفاجأة يعمل بشكل جيد هنا. ليس من المستغرب أن تعمل تركيبات جديدة بشكل مختلف.

أنت على حق بالطبع. ما قصدته هو أنه بالنسبة للنوع الأصلي من تجهيزات الاختبار ، سيظل السلوك مفاجئًا لمعظم الناس. (حدث مفاجأة لأحد زملائه في العمل منذ أسبوعين ، عندما وجد حالة اختبار تؤثر على اختبار آخر. لقد كان يستخدم NUnit منذ عام 2013 ، وهذا هو سبب بحثي عن هذه المشكلة.)

لكنها بالطبع مفاضلة بين التوافق العكسي ، وتعقيد التنفيذ ، والرغبة في تصحيح خطيئة أصلية [1].

عودة إلى قضية OneTimeSetUp : أعتقد أنه لا يزال من المهم الحصول على OneTimeSetUp لكل أداة اختبار ، حتى إذا تم إنشاء فئة تركيبات الاختبار لكل اختبار. لتجنب الالتباس في حالة قيام OneTimeSetUp بالتلاعب بحالة المثيل ، أقترح فرض OneTimeSetUpOneTimeTearDown ) ليكون ثابتًا مع ميزة المثيل لكل اختبار. (بغض النظر عما إذا تم تنفيذه كمفتاح أو كنوع جديد من تركيبات الاختبار.)


[1] كتب جيمس نيوكيرك

أعتقد أن أحد أكبر الأخطاء التي تم إجراؤها عندما كتبنا NUnit V2.0 هو عدم إنشاء مثيل جديد لفئة تركيبات الاختبار لكل طريقة اختبار مضمنة. أقول "نحن" لكنني أعتقد أن هذا كان خطأي. لم أفهم تمامًا المنطق في JUnit لإنشاء مثيل جديد من تركيبات الاختبار لكل طريقة اختبار.

يكتب هذا أيضًا ، على الرغم من:

[...] سيكون من الصعب تغيير الطريقة التي تعمل بها NUnit الآن ، سيشتكي الكثير من الناس [...]

:)

نعم ، لقد جادلت أنا وجيم حول هذا الأمر لسنوات. ابتسامة: شيء واحد نتفق عليه هو الجملة الأخيرة. ذهب جيم لوضع إطار عمل جديد قبل تنفيذ نهجه المفضل. أنا أفعل الشيء نفسه بشكل أساسي و SetUp / TearDown هو شيء من المحتمل أن أفعله بشكل مختلف.

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

إذن .. هل هذا الأمر متاحًا؟ أو لا يزال في مرحلة التصميم؟

الآن في .NET ، يمكنك الوصول إلى XUnit بدون TestContext أو NUnit ، مع اختبارات متوازية معيارية

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

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

لذا ، هل ترغب في العمل عليها؟

بالتأكيد ، لقد بدأت العمل للتو في
https://github.com/avilv/nunit/tree/instance-per-test

أنا جديد في قاعدة البيانات هذه ، لذا أحاول التمسك بنمط الكود الأصلي بقدر ما أستطيع
لقد قدمت حتى الآن سمة InstancePerTestCase وأخطط لدعمها على مستوى التجميع والفئة

اسمحوا لي أن أعرف إذا كنت بعيداً عن القاعدة هنا

أضفت تعليقات على بعض تفاصيل التنفيذ في آخر التزام لك.

فيما يتعلق بالتصميم ، أعتقد أن السمة المشتقة من IApplyToContext منطقية. أعتقد أنني أفضل شيئًا أكثر عمومية من مجرد InstancePerTestCase ، ربما

C# [FixtureLifeCycle(LifeCycle.InstancePerTestCase]

سيسمح لك ذلك بضبطه على مستوى التجميع وإعادة ضبطه على التركيبات الفردية.

CharliePoole أنت كثيرًا ، شكرًا جزيلاً على التعليقات والمساعدة

أعتقد أنني أحصل على كيفية بناء الأشياء / ماذا يفعل وما إلى ذلك ببطء ولكن بثبات

هذا هو اقتراحي لتعداد دورة الحياة (لقد قمت أيضًا بتحديث فرعي باقتراحك)

    /// <summary>
    /// Specifies the lifecycle for a fixture.
    /// </summary>
    public enum LifeCycle
    {
        /// <summary>
        /// A single instance is created and for all test cases.
        /// </summary>
        SingleInstance,

        /// <summary>
        /// A new instance is created for each test case for fixtures that are marked with <see cref="ParallelizableAttribute"/>.
        /// </summary>
        InstancePerTestCaseForParallelFixtures,

        /// <summary>
        /// A new instance is created for each test case.
        /// </summary>
        InstancePerTestCase
    }

@ jnm2 ما رأيك في هذا النهج - أنت نوع من "الراعي" لهذه الميزة في المقام الأول. أنا غير متأكد قليلاً بشأن InstancePerTestCaseForParallelFixtures ، والذي قد يكون أكثر من اللازم بالنسبة للتنفيذ الأولي. كالعادة ، بمجرد تنفيذه ، نحن عالقون فيه.

إذا __فعل__ قبلنا الارتباط بالتوازي ، فيجب أن يكون قائمًا على السياق ، وليس على أساس السمات. أي أنه يمكن إجراء أي اختبار بشكل متوازٍ ، لذا فهو أشبه بـ InstancePerTestCaseWhenCasesAreParallel. لا تتطلب التركيبات المتوازية هذه الميزة بشكل خاص. وربما من الأفضل أن يُترك الأمر للمستخدم لاتخاذ القرار.

أوافق أيضًا على أنه لا ينبغي أن يقترن بأي شكل من الأشكال بالتوازي. كان التوازي هو سبب إضافة هذه الميزة ولكن بمجرد أن نتفق على أن هذه الميزة مفيدة ، في رأيي ، لا ينبغي أن تنعكس في الكود

أوافق ، أنا أفهم حالة الاستخدام لاستخدامه فقط في السيناريوهات المتوازية حيث أن النموذج الافتراضي الحالي لـ NUnit سوف يعضك ، ولكن آمل أن تكون هذه هي دورة الحياة الافتراضية في المستقبل (nunit 4؟) لأنني أعتقد أنه يجب أن يكون بالفعل كانت بهذه الطريقة منذ البداية

ربما يجب أن نتركه في SingleInsance / InstancePerTestCase

سؤال آخر هو هل يجب أن نتعامل مع نمط IDisposable هنا ، فهذا من شأنه أن يجعل SetUp / TearDown زائدة عن الحاجة إلى حد كبير ولكنها ستشعر بشكل طبيعي أكثر بأسلوب XUnit.

نحن بالفعل نتخلص من أي تجهيزات اختبار تستخدم IDisposable. تحتاج فقط إلى التأكد من استمرار ذلك في العمل.

عظيم ، سوف تفعل.

من المحتمل أن تبدأ في إضافة حالات الاختبار اليوم ، ولكن نظرًا لأنها آلية أساسية ، فقد تضيف قدرًا كبيرًا من الحالات التي تتأكد فقط من أن نفس الشيء يعمل عند تشغيل InstancePerTestCase ، صححني إذا كنت مخطئًا

يعتني DisposeFixtureCommand باستدعاء Dispose.

IMO مشكلتك الرئيسية هي التأكد من استدعاء مجرى الأوامر بالكامل لتركيبات الاختبار __ لكل اختبار__. التغييرات التي أراها حتى الآن تُنشئ بشكل صحيح تدفق أوامر يبدو أنه يؤدي المهمة ، ولكن السؤال الرئيسي هو كيفية التأكد من أنه تم استدعاؤه لكل حالة اختبار. (لاحظ أن بنية الأوامر الخاصة بالتركيبات تختلف عن تلك الخاصة بحالات الاختبار.) أعتقد أن compositeworkitem يجب أن يكون حساسًا للسياق واستدعاء الأمر بشكل صحيح (كما فعلت) ولكني لست متأكدًا من أنه سيحصل على فرصة للقيام بذلك في __كل _ حالة اختبار. (أنا أقرأ الكود فقط ، لذا قد أكون مخطئًا).

يبدو أن CharliePoole على حق ، لقد قمت بعمل رسم بياني بسيط لما ينفذ بالضبط طريقة الاختبار:
image

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

لقد قمت بتحديث أحدث التغييرات في
https://github.com/avilv/nunit/tree/instance-per-test-2

تمت إزالة InstancePerTestCaseWhenCasesAreParallel
نفذت FixtureLifeCycle عن طريق إضافة تركيبات Construct / Dispose داخل MakeTestCommand في SimpleWorkItem ، CompositeWorkItem MakeOneTimeSetUpCommand

يبدو أنه يعمل بشكل جيد ، كما أنه يكون أكثر منطقية مع كيفية إنشاء إطار العمل
الآن أنا فقط بحاجة إلى إضافة مجموعة من اختبارات الوحدة للتأكد من أنها تعمل بشكل صحيح

هل ستتم مراجعة هذا قبل الإصدار التالي؟ ما زلت على استعداد لإجراء تحسينات / تغييرات بالطبع

يبدو هذا الأسلوب جيدًا بالنسبة لي وأود استخدامه في الإصدار التالي إذا كان بإمكاننا القيام بذلك!

هل من الممكن أن نتمكن من مراجعة هذا؟ أشعر وكأنها تقترب حقًا

هل تشعر بالفضول إذا كانت هناك أي حركة على هذه الميزة؟ يبدو أن avilv قد اقترب منها كثيرًا. لقد كنت أتوقع ميزة مثل هذه لفترة طويلة ، وكنت متحمسًا لاستخدامها!

شكرا لكم جميعا!

هل هذه الميزة تعمل الآن؟

CharliePoole هل يمكنك مراجعة أحدث إصدار من avilv ؟ يبدو أنه تم تنفيذه بالكامل. أعتقد أن الكثير من الناس ينتظرون هذه الميزة.

janissimsons معذرة ، لكنني لم أعد في هذا المشروع بعد الآن ، لذا فإن

أقدر كل العمل الذي تم إنجازه لدمج #

كيف نستخدم الميزة الجديدة؟ هل يمكن لشخص أن يكتب وصفًا موجزًا ​​للسمات الجديدة وكيفية استخدامها بشكل صحيح؟

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

rprouse هل هناك أي تقديرات متى ستطلق الإصدار الجديد؟ أود البدء في استخدام هذه الميزة.

تضمين التغريدة نحن نعمل على حل بعض المشكلات مع إصدارات .NET 5.0 الخاصة بنا وبعض المشكلات الأخرى ، ثم سأقوم بإصدارها. يمكنك تتبع الحالة على لوحة المشروع هذه ، https://github.com/nunit/nunit/projects/4

هذه أخبار رائعة!

هل أفهم بشكل صحيح أن هذا سيجعل ما يلي ممكنًا في NUnit؟

اجمع كل هذه:

  • الاختبارات معلمات
  • قم بإجراء الاختبارات بشكل متوازٍ.
  • أن تكون قادرًا على تشغيل متغيرات ذات معلمات لنفس الاختبار بالتوازي.
  • يمكن أن تكون الاختبارات مدفوعة بالبيانات

جربت بعض الأشياء في الماضي هنا https://github.com/jawn/dotnet-parallel-parameterized-tests

هل سيظل الإصدار الجديد متوافقًا مع .NET Full Framework 4.7.2 أو 4.8 على الأقل؟

تحياتي الحارة،
الدكتور

jawn أعتقد أن كل هذا

drauch نعم ، سيظل الإصدار 3.13 يدعم عودة .NET 3.5.

avilv عمل عظيم !!
بالمناسبة باستخدام الأداة التي قمت بإنشاء مخطط التدفق الجميل هذا؟
شكرا

rprouse هل يمكنك أيضًا تحديث الوثائق حول كيفية استخدام هذه الميزة الجديدة؟ شكرا!

janissimsons أخطط لتحديث مستندات الإصدار. على الرغم من أن الإصدار القصير هو إضافة [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] إلى فئة الاختبار الخاصة بك ، ربما مع [Parallelizable(ParallelScope.All)] وسيتم إنشاء مثيل لفئة الاختبار الخاصة بك لكل اختبار يتم تشغيله داخل الفصل الدراسي. يتيح لك القيام بذلك استخدام متغيرات الأعضاء في الفصل في اختبارات متوازية دون القلق بشأن الاختبارات الأخرى التي تغير المتغيرات.

LirazShay تم إنشاء الرسم التخطيطي في لقطة الشاشة في Visual Studio Enterprise. يمكنك النقر بزر الماوس الأيمن فوق تحديد في "مستكشف الحلول" واختيار "إظهار على خريطة الكود" ، على سبيل المثال. https://docs.microsoft.com/en-us/visualstudio/modeling/map-dependencies-across-your-solutions ReSharper لديه ميزة مماثلة.

@ jnm2 شكرا جزيلا !!!
لدي مؤسسة VS ولم أكن أعرف عن هذه الميزة التي يمكن أن تكون مفيدة للغاية
شكرا جزيلا لهذه المعلومة لك

هل أنا محق في أن هذا قد تم إصداره وهو جزء من الإصدار 3.13؟

هل أنا محق في أن هذا قد تم إصداره وهو جزء من الإصدار 3.13؟

أعتقد أنه كانت هناك مشكلتان تمت ملاحظتهما وتم إصلاحهما ، خاصة فيما يتعلق بتطبيق سمات مستوى التجميع في # 3720 ونحن ننتظر 3.13.1 الآن ..

تم إصدار NUnit 3.13.1.
هل هناك المزيد من القضايا المتبقية؟

لا لهذا السبب تم إغلاقه andrewlaser

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