سؤال

أنا أستخدم specflow ، وأرغب في كتابة سيناريو مثل ما يلي:

Scenario: Pressing add with an empty stack throws an exception
    Given I have entered nothing into the calculator
    When I press add
    Then it should throw an exception

إنه calculator.Add() هذا سوف يلقي استثناء ، فكيف أتعامل مع هذا في الطريقة المحددة [Then]?

هل كانت مفيدة؟

المحلول

سؤال عظيم. أنا لست خبيرًا في BDD أو Specflow ، ومع ذلك ، فإن نصيحتي الأولى هي التراجع وتقييم السيناريو الخاص بك.

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

فكر في تغيير عبارة "ثم" لتضمين شيء مثل هذا:

Scenario: Pressing add with an empty stack displays an error
    Given I have entered nothing into the calculator
    When I press add
    Then the user is presented with an error message

لا يزال يتم طرح الاستثناء في الخلفية ولكن النتيجة النهائية هي رسالة خطأ بسيطة.

يمس Scott Bellware هذا المفهوم في بودكاست رمز الرعي هذا: http://herdingcode.com/؟p=176

نصائح أخرى

بصفتك مبتدئًا في Specflow ، لن أخبرك أن هذا ال طريقة للقيام بذلك ، ولكن طريقة واحدة للقيام بذلك هي استخدام ScenarioContext لتخزين الاستثناء الذي تم إلقاؤه في متي;

try
{
    calculator.Add(1,1);
}
catch (Exception e)
{
    ScenarioContext.Current.Add("Exception_CalculatorAdd", e);
}

في الخاص بك ثم يمكنك التحقق من الاستثناء الذي تم إلقاؤه ويؤكد عليه ؛

var exception = ScenarioContext.Current["Exception_CalculatorAdd"];
Assert.That(exception, Is.Not.Null);

مع ذلك ؛ أنا أتفق مع Scoarescoare عندما يقول أنه يجب عليك صياغة السيناريو في كلمات "صديقة للأعمال" أكثر قليلاً. ومع ذلك ، فإن استخدام Specflow لدفع تنفيذ نموذج المجال الخاص بك ، والتقاط الاستثناءات والتأكيدات عليها ، يمكن أن يكون مفيدًا.

راجع للشغل: تحقق من Screencast من Rob Conery في Tekpub للحصول على بعض النصائح الجيدة حقًا حول استخدام Specflow: http://tekpub.com/view/concepts/5

يمكن ممارسة BDD على سلوك مستوى الميزة أو/على سلوك مستوى الوحدة.

Specflow هي أداة BDD التي تركز على سلوك مستوى الميزة. الاستثناءات ليست شيئًا يجب عليك تحديده/ملاحظته على سلوك مستوى الميزة. يجب تحديد/ملاحظ الاستثناءات على سلوك مستوى الوحدة.

فكر في سيناريوهات Specflow كمواصفات حية لأصحاب المصلحة غير التقني. كما أنك لن تكتب في المواصفات التي يتم طرح استثناء ، ولكن كيف يتصرف النظام في مثل هذه الحالة.

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

هناك أدوات BDD التي تركز على سلوك مستوى الوحدة. في .NET الأكثر شعبية هو MSPEC (http://github.com/machine/machine.specifications). يمكن أيضًا أن تكون BDD على مستوى الوحدة ممارسات مع أطر عمل اختبار للوحدة القياسية.

ومع ذلك ، أنت لا يزال من الممكن التحقق من استثناء في Specflow.

فيما يلي المزيد من النقاش حول BDD على مستوى الوحدة مقابل BDD على مستوى الميزة:Specflow/BDD vs اختبار الوحدة BDD لاختبارات القبول مقابل BDD لاختبارات الوحدة (أو: ATDD مقابل TDD)

إلقاء نظرة على منشور المدونة هذا:تصنيف أدوات BDD (لاختبار الوحدة مقابل اختبار القبول) وقليلا من تاريخ BDD

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

  1. احصل على استثناء (أوصي حقًا بالتقاط استثناءات محددة ما لم تكن بحاجة حقًا إلى التقاط الجميع) في الخطوة التي تستدعي العملية ونقلها إلى سياق السيناريو.

    [When("I press add")]
    public void WhenIPressAdd()
    {
       try
       {
         _calc.Add();
       }
       catch (Exception err)
       {
          ScenarioContext.Current[("Error")] = err;
       }
    }
    
  2. تم التحقق من صحة هذا الاستثناء في سياق السيناريو

    [Then(@"it should throw an exception")]
    public void ThenItShouldThrowAnException()
    {
          Assert.IsTrue(ScenarioContext.Current.ContainsKey("Error"));
    }
    

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

var err = ScenarioContext.Current["Error"]

سوف يلقي استثناء آخر في حالة عدم وجود مفتاح "خطأ" (وسيخفق ذلك في جميع السيناريوهات التي تؤدي الحسابات باستخدام المعلمات الصحيحة). لذا ScenarioContext.Current.ContainsKey قد يكون أكثر ملاءمة

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

أنا أستخدم Specflow لتطوير طبقة عمل. في حالتي ، لا يهمني تفاعلات واجهة المستخدم ، لكنني ما زلت أجد مفيدًا للغاية في نهج BDD و specflow.

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

حاليا ، أنا أستخدم صريحًا "ثم" بنود ، في بعض الأحيان بدون "متى" ، بهذه الطريقة:

Scenario: Adding with an empty stack causes an error
     Given I have entered nothing into the calculator
     Then adding causes an error X

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

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

@CatchException
Scenario: Faulty operation throws exception
    Given Some Context
    When Some faulty operation invoked
    Then Exception thrown with type 'ValidationException' and message 'Validation failed'

لجعل هذا العمل ، اتبع تلك الخطوات الثلاث:

الخطوة 1

مارك سيناريوهات تتوقع استثناءات مع بعض العلامات ، على سبيل المثال @CatchException:

@CatchException
Scenario: ...

الخطوة 2

تحديد AfterStep معالج للتغيير ScenarioContext.TestStatus أن تكون OK. قد ترغب فقط في تجاهل الأخطاء في متي الخطوات ، لذلك لا يزال بإمكانك فشل الاختبار ثم التحقق من استثناء. اضطررت إلى القيام بذلك من خلال الانعكاس TestStatus الممتلكات داخلية:

[AfterStep("CatchException")]
public void CatchException()
{
    if (ScenarioContext.Current.StepContext.StepInfo.StepDefinitionType == StepDefinitionType.When)
    {
        PropertyInfo testStatusProperty = typeof(ScenarioContext).GetProperty("TestStatus", BindingFlags.NonPublic | BindingFlags.Instance);
        testStatusProperty.SetValue(ScenarioContext.Current, TestStatus.OK);
    }
}

الخطوه 3

التحقق TestError بنفس الطريقة التي ستتحقق بها من أي شيء في الداخل ScenarioContext.

[Then(@"Exception thrown with type '(.*)' and message '(.*)'")]
public void ThenExceptionThrown(string type, string message)
{
    Assert.AreEqual(type, ScenarioContext.Current.TestError.GetType().Name);
    Assert.AreEqual(message, ScenarioContext.Current.TestError.Message);
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top