سؤال

كيف يمكنني استخدام تأكيد (أو فئة اختبار أخرى؟) للتحقق من إلقاء استثناء؟

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

المحلول

بالنسبة إلى "اختبار فريق Visual Studio"، يظهر لك تطبيق سمة RecuteException لطريقة الاختبار.

عينة من الوثائق هنا: تجول اختبار الوحدة مع اختبار فريق Visual Studio

[TestMethod]
[ExpectedException(typeof(ArgumentException),
    "A userId of null was inappropriately allowed.")]
public void NullUserIdInConstructor()
{
   LogonInfo logonInfo = new LogonInfo(null, "P@ss0word");
}

نصائح أخرى

عادة ما يكون لإطار الاختبار الخاص بك إجابة لهذا. ولكن إذا لم يكن مرنا بما فيه الكفاية، فيمكنك دائما القيام بذلك:

try {
    somethingThatShouldThrowAnException();
    Assert.Fail(); // If it gets to this line, no exception was thrown
} catch (GoodException) { }

كما يشير Jonas، هذا لا يعمل لالتقاط استثناء أساسي:

try {
    somethingThatShouldThrowAnException();
    Assert.Fail(); // raises AssertionException
} catch (Exception) {
    // Catches the assertion exception, and the test passes
}

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

catch (AssertionException) { throw; }

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

} catch (GoodException) {
} catch (Exception) {
    // not the right kind of exception
    Assert.Fail();
}

الطريقة المفضلة لتنفيذ هذا هي كتابة طريقة تسمى الرميات، واستخدامها تماما مثل أي طريقة تأكيد أخرى. لسوء الحظ، لا يسمح لك .NET بكتابة طريقة تمديد ثابت، حتى لا تتمكن من استخدام هذه الطريقة كما لو أنها تنتمي في الواقع إلى Build In Conster؛ مجرد جعل آخر يسمى myassert أو شيء مماثل. فئة تبدو وكأنها هذه:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace YourProject.Tests
{
    public static class MyAssert
    {
        public static void Throws<T>( Action func ) where T : Exception
        {
            var exceptionThrown = false;
            try
            {
                func.Invoke();
            }
            catch ( T )
            {
                exceptionThrown = true;
            }

            if ( !exceptionThrown )
            {
                throw new AssertFailedException(
                    String.Format("An exception of type {0} was expected, but not thrown", typeof(T))
                    );
            }
        }
    }
}

هذا يعني أن اختبار الوحدة الخاص بك يشبه هذا:

[TestMethod()]
public void ExceptionTest()
{
    String testStr = null;
    MyAssert.Throws<NullReferenceException>(() => testStr.ToUpper());
}

التي تبدو وتصرف أكثر بكثير مثل بقية وضع جملة اختبار الوحدة الخاصة بك.

إذا كنت تستخدم MSTest، والذي لم يكن لديك في الأصل ExpectedException السمة، يمكنك القيام بذلك:

try 
{
    SomeExceptionThrowingMethod()
    Assert.Fail("no exception thrown");
}
catch (Exception ex)
{
    Assert.IsTrue(ex is SpecificExceptionType);
}

إذا كنت تستخدم nunit، فيمكنك القيام بشيء مثل هذا:

Assert.Throws<ExpectedException>(() => methodToTest());


من الممكن أيضا تخزين الاستثناء القيت من أجل التحقق من صحة ذلك:

ExpectedException ex = Assert.Throws<ExpectedException>(() => methodToTest());
Assert.AreEqual( "Expected message text.", ex.Message );
Assert.AreEqual( 5, ex.SomeNumber);

يرى: http://nunit.org/docs/2.5/ExceptionSerts.html.

كن حذرا من استخدام التوقعات، حيث يمكن أن يؤدي إلى العديد من المزالق كما هو موضح هنا:

http://geekswithblogs.net/sdorman/archive/2009/01/17/unit-testing-and-expected-exceptions.aspx.

و هنا:

http://xunit.github.io/docs/comparisons.html.

إذا كنت بحاجة إلى اختبار الاستثناءات، فهناك أقل عبوس عند طرق. يمكنك استخدام طريقة CREY {ACT / FACH} Catch {Assert}، والتي يمكن أن تكون مفيدة للأطر التي لا تحتوي على دعم مباشر لاختبارات الاستثناءات غير المتوقعة.

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

يمكنك العثور على Xunit.net في Github: http://xunit.github.io/

في مشروع أعمل عليه، لدينا حل آخر القيام بذلك.

أولا لا أحب التوقعات الفاستيكيةأتربية لأنها تأخذ في الاعتبار الطريقة الطريقة التي تسبب الاستثناء.

أفعل هذا مع helpermethod بدلا من ذلك.

اختبار

[TestMethod]
public void AccountRepository_ThrowsExceptionIfFileisCorrupt()
{
     var file = File.Create("Accounts.bin");
     file.WriteByte(1);
     file.Close();

     IAccountRepository repo = new FileAccountRepository();
     TestHelpers.AssertThrows<SerializationException>(()=>repo.GetAll());            
}

helpermethod.

public static TException AssertThrows<TException>(Action action) where TException : Exception
    {
        try
        {
            action();
        }
        catch (TException ex)
        {
            return ex;
        }
        Assert.Fail("Expected exception was not thrown");

        return null;
    }

أنيق، أليس كذلك؛

يحتوي MSTest (V2) الآن وظيفة تأكيد.منوعة يمكن استخدامها مثل هذا:

Assert.ThrowsException<System.FormatException>(() =>
            {
                Story actual = PersonalSite.Services.Content.ExtractHeader(String.Empty);
            }); 

يمكنك تثبيته مع Nuget: Install-Package MSTest.TestFramework

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

[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()

يمكنك تنزيل حزمة من Nuget باستخدام: PM> تثبيت حزمة mstestextensions هذا يضيف assert.throws () بناء جملة في أسلوب nunit / xunit إلى mstest.

تعليمات عالية المستوى: قم بتنزيل التجميع والوثي من basetest. ويمكنك استخدام assert.throws () بناء الجملة.

تبدو الطريقة الرئيسية لتنفيذ الرميات كما يلي:

public static void Throws<T>(Action task, string expectedMessage, ExceptionMessageCompareOptions options) where T : Exception
{
    try
    {
        task();
    }
    catch (Exception ex)
    {
        AssertExceptionType<T>(ex);
        AssertExceptionMessage(ex, expectedMessage, options);
        return;
    }

    if (typeof(T).Equals(new Exception().GetType()))
    {
        Assert.Fail("Expected exception but no exception was thrown.");
    }
    else
    {
        Assert.Fail(string.Format("Expected exception of type {0} but no exception was thrown.", typeof(T)));
    }
}

الإفصاح: وضعت هذه الحزمة.

مزيد من المعلومات: http://www.bradoncode.com/blogcoy2/01/asserting-exceptions-in-mstest-with.html.

يمكنك تحقيق ذلك مع سطر واحد بسيط.

إذا كانت العملية الخاصة بك foo.bar() هو Async:

await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());

إذا foo.bar() ليس Async.

Assert.ThrowsException<Exception>(() => foo.bar());

لا أوصي باستخدام سمة RecectException (نظرا لأنها تقارب وعرضة للخطأ) أو كتابة كتلة TRY / CATCH في كل اختبار (نظرا لأنها معقدة للغاية وخطأ). استخدم طريقة تأكيد مصممة جيدا - إما توفرها إطار الاختبار الخاص بك أو اكتب بنفسك. إليك ما كتبته واستخدامه.

public static class ExceptionAssert
{
    private static T GetException<T>(Action action, string message="") where T : Exception
    {
        try
        {
            action();
        }
        catch (T exception)
        {
            return exception;
        }
        throw new AssertFailedException("Expected exception " + typeof(T).FullName + ", but none was propagated.  " + message);
    }

    public static void Propagates<T>(Action action) where T : Exception
    {
        Propagates<T>(action, "");
    }

    public static void Propagates<T>(Action action, string message) where T : Exception
    {
        GetException<T>(action, message);
    }

    public static void Propagates<T>(Action action, Action<T> validation) where T : Exception
    {
        Propagates(action, validation, "");
    }

    public static void Propagates<T>(Action action, Action<T> validation, string message) where T : Exception
    {
        validation(GetException<T>(action, message));
    }
}

المثال يستخدم:

    [TestMethod]
    public void Run_PropagatesWin32Exception_ForInvalidExeFile()
    {
        (test setup that might propagate Win32Exception)
        ExceptionAssert.Propagates<Win32Exception>(
            () => CommandExecutionUtil.Run(Assembly.GetExecutingAssembly().Location, new string[0]));
        (more asserts or something)
    }

    [TestMethod]
    public void Run_PropagatesFileNotFoundException_ForExecutableNotFound()
    {
        (test setup that might propagate FileNotFoundException)
        ExceptionAssert.Propagates<FileNotFoundException>(
            () => CommandExecutionUtil.Run("NotThere.exe", new string[0]),
            e => StringAssert.Contains(e.Message, "NotThere.exe"));
        (more asserts or something)
    }

ملاحظات

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

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

الفكر النهائي

قبل التبديل إلى هذا النوع من النهج الذي فكرت فيه باستخدام سمة RecectException عندما تحقق اختبار فقط من نوع الاستثناء واستخدام كتلة TRY / CATCK إذا كان مطلوبا أكثر التحقق من الصحة. ولكن، ليس فقط يجب أن أفكر في أي تقنية للاستخدام لكل اختبار، ولكن تغيير الكود من تقنية واحدة إلى أخرى كما تغير الاحتياجات لم يكن جهدا تافها. باستخدام نهج واحد ثابت يحفظ الجهد العقلي.

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

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

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace YourProject.Tests
{
    public static class MyAssert
    {
        /// <summary>
        /// Helper for Asserting that a function throws an exception of a particular type.
        /// </summary>
        public static void Throws<T>( Action func ) where T : Exception
        {
            Exception exceptionOther = null;
            var exceptionThrown = false;
            try
            {
                func.Invoke();
            }
            catch ( T )
            {
                exceptionThrown = true;
            }
            catch (Exception e) {
                exceptionOther = e;
            }

            if ( !exceptionThrown )
            {
                if (exceptionOther != null) {
                    throw new AssertFailedException(
                        String.Format("An exception of type {0} was expected, but not thrown. Instead, an exception of type {1} was thrown.", typeof(T), exceptionOther.GetType()),
                        exceptionOther
                        );
                }

                throw new AssertFailedException(
                    String.Format("An exception of type {0} was expected, but no exception was thrown.", typeof(T))
                    );
            }
        }
    }
}

منذ أن تذكر باستخدام فئات الاختبار الأخرى، خيار أفضل من ExpectedException السمة هي استخدام shoudly.يجب أن.

Should.Throw<DivideByZeroException>(() => { MyDivideMethod(1, 0); });

دعنا نقول أن لدينا شرط أن عميل يجب أن يكون لديك عنوان لإنشاء an. ترتيب. وبعد إن لم يكن، CreateOrderForCustomer الطريقة يجب أن تؤدي إلى ArgumentException. وبعد ثم يمكننا أن نكتب:

[TestMethod]
public void NullUserIdInConstructor()
{
  var customer = new Customer(name := "Justin", address := null};

  Should.Throw<ArgumentException>(() => {
    var order = CreateOrderForCustomer(customer) });
}

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

ملاحظة هناك أيضا Should.ThrowAsync للاختبار الطريقة غير المتزامنة.

كبديل، يمكنك تجربة الاستثناءات الاختبار في الواقع يتم طرحها مع السطرين المقبلين في الاختبار.

var testDelegate = () => MyService.Method(params);
Assert.Throws<Exception>(testDelegate);

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

/// <summary>
/// Checks to make sure that the input delegate throws a exception of type TException.
/// </summary>
/// <typeparam name="TException">The type of exception expected.</typeparam>
/// <param name="methodToExecute">The method to execute to generate the exception.</param>
public static void AssertRaises<TException>(Action methodToExecute) where TException : System.Exception
{
    try
    {
        methodToExecute();
    }
    catch (TException) {
        return;
    }  
    catch (System.Exception ex)
    {
        Assert.Fail("Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
    }
    Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");  
}

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

[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void ThrowExceptionTest()
{
    //...
}

الدفع مستندات nunit. للحصول على أمثلة حول:

[ExpectedException( typeof( ArgumentException ) )]

هذا سوف يعتمد على إطار الاختبار الذي تستخدمه؟

في MBUNIT، على سبيل المثال، يمكنك تحديد الاستثناء المتوقع مع سمة للتأكد من أنك تحصل على الاستثناء الذي تتوقعه حقا.

[ExpectedException(typeof(ArgumentException))]

في حالة استخدام nunit, ، جرب هذا:

Assert.That(() =>
        {
            Your_Method_To_Test();
        }, Throws.TypeOf<Your_Specific_Exception>().With.Message.EqualTo("Your_Specific_Message"));

على الرغم من أن هذا سؤال قديم، أود أن أضيف فكرة جديدة للمناقشة. لقد مددت ترتيب، ACT، تأكيد نمط من المتوقع، وترتيب، ACT، أكد. يمكنك إجراء مؤشر استثناء متوقع، ثم أكد أنه تم تعيينه إليه. يشعر هذا بالتنظيف من القيام بتأكيدك في كتلة الصيد، مما يترك قسم الفعل الخاص بك في الغالب فقط لسطر التعليمات البرمجية لاتصال الطريقة قيد الاختبار. أنت أيضا لا تضطر إلى Assert.Fail(); أو return من نقاط متعددة في التعليمات البرمجية. أي استثناء آخر يتم إلقاؤه سيؤدي إلى فشل الاختبار، لأنه لن يتم القبض عليه، وإذا تم إلقاء استثناء من نوعك المتوقع، ولكن لم يكن الأمر هو الذي كنت تتوقعه، يؤكد ضد الرسالة أو خصائص أخرى ل تساعد الاستثناء في التأكد من عدم تجاوز الاختبار عن غير قصد.

[TestMethod]
public void Bar_InvalidDependency_ThrowsInvalidOperationException()
{
    // Expectations
    InvalidOperationException expectedException = null;
    string expectedExceptionMessage = "Bar did something invalid.";

    // Arrange
    IDependency dependency = DependencyMocks.Create();
    Foo foo = new Foo(dependency);

    // Act
    try
    {
        foo.Bar();
    }
    catch (InvalidOperationException ex)
    {
        expectedException = ex;
    }

    // Assert
    Assert.IsNotNull(expectedException);
    Assert.AreEqual(expectedExceptionMessage, expectedException.Message);
}

هناك مكتبة رهيبة تسمى nfluent أي يسرع ويخفف الطريقة التي تكتبها تأكيداتك.

من الواضح جدا كتابة تأكيد لإلقاء استثناء:

    [Test]
    public void given_when_then()
    {
        Check.ThatCode(() => MethodToTest())
            .Throws<Exception>()
            .WithMessage("Process has been failed");
    }
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top