Cách tốt nhất để kiểm tra các ngoại lệ với Assert để đảm bảo chúng sẽ được ném


97

Bạn có nghĩ rằng đây là một cách tốt để kiểm tra các trường hợp ngoại lệ? Bất kỳ đề xuất?

Exception exception = null;
try{
    //I m sure that an exeption will happen here
}
catch (Exception ex){
    exception = ex;
}

Assert.IsNotNull(exception);

Tôi đang sử dụng MS Test.

Câu trả lời:


137

Tôi có một vài mẫu khác nhau mà tôi sử dụng. Tôi sử dụng ExpectedExceptionthuộc tính hầu hết thời gian khi mong đợi một ngoại lệ. Điều này đủ cho hầu hết các trường hợp, tuy nhiên, có một số trường hợp điều này là không đủ. Ngoại lệ có thể không bắt được - vì nó được ném bởi một phương thức được gọi bởi phản xạ - hoặc có lẽ tôi chỉ muốn kiểm tra xem các điều kiện khác có giữ hay không, giả sử một giao dịch được khôi phục hoặc một số giá trị vẫn được đặt. Trong những trường hợp này, tôi bọc nó trong một try/catchkhối mong đợi chính xác ngoại lệ, thực hiện Assert.Failnếu mã thành công và cũng bắt các ngoại lệ chung để đảm bảo rằng một ngoại lệ khác không được ném ra.

Trường hợp đầu tiên:

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void MethodTest()
{
     var obj = new ClassRequiringNonNullParameter( null );
}

Trường hợp thứ hai:

[TestMethod]
public void MethodTest()
{
    try
    {
        var obj = new ClassRequiringNonNullParameter( null );
        Assert.Fail("An exception should have been thrown");
    }
    catch (ArgumentNullException ae)
    {
        Assert.AreEqual( "Parameter cannot be null or empty.", ae.Message );
    }
    catch (Exception e)
    {
        Assert.Fail(
             string.Format( "Unexpected exception of type {0} caught: {1}",
                            e.GetType(), e.Message )
        );
    }
}

16
Nhiều khuôn khổ kiểm thử đơn vị thực hiện các lỗi xác nhận là ngoại lệ. Vì vậy, Assert.Fail () trong trường hợp thứ hai sẽ bị chặn bởi khối catch (Exception), khối này sẽ ẩn thông báo ngoại lệ. Bạn cần thêm một hàm bắt (NUnit.Framework.AssertionException) {ném;} hoặc tương tự - hãy xem câu trả lời của tôi.
GrahamS

@Graham - Tôi đã gõ cái này trên đỉnh đầu của mình. Thông thường, tôi cũng sẽ in ra thông báo ngoại lệ ngoài loại nó. Vấn đề là kiểm tra sẽ thất bại vì trình xử lý thứ hai sẽ bắt lỗi xác nhận và "phản hồi" với thông tin về lỗi.
tvanfosson

1
Mặc dù mã của bạn hoạt động tốt, tôi không khuyên bạn nên sử dụng thuộc tính Exception (vì nó quá hạn chế và dễ xảy ra lỗi) hoặc viết khối try / catch trong mỗi lần kiểm tra (vì nó quá phức tạp và dễ xảy ra lỗi). Sử dụng phương pháp xác nhận được thiết kế tốt - do khung thử nghiệm của bạn cung cấp hoặc do chính bạn viết. Bạn có thể đạt được mã tốt hơn và bạn sẽ không phải chọn giữa các kỹ thuật khác nhau hoặc thay đổi từ kỹ thuật này sang kỹ thuật khác khi thử nghiệm thay đổi. Xem stackoverflow.com/a/25084462/2166177
steve

FYI - Tôi đã chuyển sang sử dụng xUnit có một Assert.Throwsphương thức được gõ mạnh bao gồm cả hai trường hợp này.
tvanfosson

Thuộc tính DraftException là cách khó chịu và có ngày tháng để kiểm tra xem liệu các ngoại lệ có được ném ra hay không. Xem câu trả lời đầy đủ của tôi bên dưới.
bytedev

41

Bây giờ, năm 2017, bạn có thể làm điều đó dễ dàng hơn với Khung MSTest V2 mới :

Assert.ThrowsException<Exception>(() => myClass.MyMethodWithError());

//async version
await Assert.ThrowsExceptionAsync<SomeException>(
  () => myObject.SomeMethodAsync()
);

Điều này sẽ chỉ thành công nếu một System.Exceptionđược ném. Bất kỳ cái nào khác, như một System.ArgumentExceptionsẽ không đạt được bài kiểm tra.
sschoof

2
Nếu bạn đang mong đợi một kiểu ngoại lệ khác, bạn nên kiểm tra nó ... Trong ví dụ của bạn, bạn thực hiện: Assert.ThrowsException <ArgumentException> (() => myClass.MyMethodWithError ());
Icaro Bombonato 20/02/19

2
Một điều quan trọng cần lưu ý là việc sử dụng Assert.ThrowsException<MyException>will chỉ kiểm tra đối với chính loại ngoại lệ được cung cấp, chứ không phải bất kỳ loại ngoại lệ bắt nguồn nào của nó. Trong ví dụ của tôi, nếu thử nghiệm SubThrowmột MyInheritedException(kiểu dẫn xuất từ ​​lớp cơ sở MyException), thì thử nghiệm sẽ không thành công .
Ama

Nếu bạn muốn mở rộng thử nghiệm của mình và chấp nhận một loại ngoại lệ cũng như các loại dẫn xuất của nó, hãy sử dụng a Try { SubToTest(); Assert.Fail("...") } Catch (AssertFailedException e) {throw;} Catch (MyException e) {...}. Lưu ý tầm quan trọng hàng đầu của Catch (AssertFailedException e) {throw;}(xem nhận xét từ allgeek)
Ama

17

Tôi là người mới ở đây và không có danh tiếng để bình luận hay phản đối, nhưng muốn chỉ ra một lỗ hổng trong ví dụ trong câu trả lời của Andy White :

try
{
    SomethingThatCausesAnException();
    Assert.Fail("Should have exceptioned above!");
}
catch (Exception ex)
{
    // whatever logging code
}

Trong tất cả các khuôn khổ kiểm thử đơn vị mà tôi quen thuộc, Assert.Failhoạt động bằng cách đưa ra một ngoại lệ, do đó, việc bắt chung sẽ thực sự che dấu sự thất bại của thử nghiệm. Nếu SomethingThatCausesAnException()không ném, ý Assert.Failchí, nhưng điều đó sẽ không bao giờ bong ra cho người chạy thử để chỉ ra sự thất bại.

Nếu bạn cần bắt ngoại lệ dự kiến ​​(tức là để xác nhận một số chi tiết nhất định, như thông báo / thuộc tính trên ngoại lệ), điều quan trọng là phải bắt loại dự kiến ​​cụ thể, chứ không phải lớp ngoại lệ cơ sở. Điều đó sẽ cho phép Assert.Failngoại lệ bong bóng (giả sử bạn không ném cùng một loại ngoại lệ mà khung thử nghiệm đơn vị của bạn thực hiện), nhưng vẫn cho phép xác thực ngoại lệ đã được đưa ra bởi SomethingThatCausesAnException()phương pháp của bạn .


15

Kể từ phiên bản 2.5, NUnit có các cấp phương pháp sau Assertđể kiểm tra các ngoại lệ:

Assert.Throws , sẽ kiểm tra một loại ngoại lệ chính xác:

Assert.Throws<NullReferenceException>(() => someNullObject.ToString());

Assert.Catch, sẽ kiểm tra một ngoại lệ của một loại nhất định hoặc một loại ngoại lệ bắt nguồn từ loại này:

Assert.Catch<Exception>(() => someNullObject.ToString());

Ngoài ra, khi gỡ lỗi các bài kiểm tra đơn vị đưa ra ngoại lệ, bạn có thể muốn ngăn VS phá vỡ ngoại lệ .

Biên tập

Chỉ để đưa ra một ví dụ về nhận xét của Matthew bên dưới, trả về của giá trị chung Assert.ThrowsAssert.Catchlà ngoại lệ với loại ngoại lệ, sau đó bạn có thể kiểm tra để kiểm tra thêm:

// The type of ex is that of the generic type parameter (SqlException)
var ex = Assert.Throws<SqlException>(() => MethodWhichDeadlocks());
Assert.AreEqual(1205, ex.Number);

2
Roy Osherove đề xuất điều này trong The Art of Unit Testing, ấn bản thứ hai, phần 2.6.2.
Avi,

2
Tôi thích Assert.Throws, ngoài ra nó trả về ngoại lệ để bạn có thể viết thêm các xác nhận về chính ngoại lệ đó.
Matthew

Câu hỏi dành cho MSTest không phải NUnit.
bytedev

Câu hỏi ban đầu của @nashwan OP không đủ tiêu chuẩn đó và việc gắn thẻ vẫn không đủ điều kiện MS-Test. Như nó viết tắt, nó là một câu hỏi C #, .Net, Unit-Testing.
StuartLC

11

Thật không may, MSTest VẪN chỉ thực sự có thuộc tính DraftException (chỉ hiển thị mức độ quan tâm của MS đến MSTest) mà IMO khá tệ vì nó phá vỡ mẫu Sắp xếp / Hành động / Xác nhận và nó không cho phép bạn chỉ định chính xác dòng mã nào bạn mong đợi ngoại lệ xảy ra trên.

Khi tôi đang sử dụng (/ bị khách hàng ép buộc) sử dụng MSTest, tôi luôn sử dụng lớp trợ giúp này:

public static class AssertException
{
    public static void Throws<TException>(Action action) where TException : Exception
    {
        try
        {
            action();
        }
        catch (Exception ex)
        {
            Assert.IsTrue(ex.GetType() == typeof(TException), "Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
            return;
        }
        Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");
    }

    public static void Throws<TException>(Action action, string expectedMessage) where TException : Exception
    {
        try
        {
            action();
        }
        catch (Exception ex)
        {
            Assert.IsTrue(ex.GetType() == typeof(TException), "Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
            Assert.AreEqual(expectedMessage, ex.Message, "Expected exception with a message of '" + expectedMessage + "' but exception with message of '" + ex.Message + "' was thrown instead.");
            return;
        }
        Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");
    }
}

Ví dụ về cách sử dụng:

AssertException.Throws<ArgumentNullException>(() => classUnderTest.GetCustomer(null));

10

Để thay thế cho việc sử dụng ExpectedExceptionthuộc tính, đôi khi tôi xác định hai phương pháp hữu ích cho các lớp thử nghiệm của mình:

AssertThrowsException() nhận một đại biểu và khẳng định rằng nó ném ngoại lệ dự kiến ​​với thông báo dự kiến.

AssertDoesNotThrowException() lấy cùng một đại biểu và khẳng định rằng nó không ném ra một ngoại lệ.

Việc ghép nối này có thể rất hữu ích khi bạn muốn kiểm tra rằng một ngoại lệ được đưa ra trong một trường hợp, chứ không phải trường hợp khác.

Sử dụng chúng, mã kiểm tra đơn vị của tôi có thể trông giống như sau:

ExceptionThrower callStartOp = delegate(){ testObj.StartOperation(); };

// Check exception is thrown correctly...
AssertThrowsException(callStartOp, typeof(InvalidOperationException), "StartOperation() called when not ready.");

testObj.Ready = true;

// Check exception is now not thrown...
AssertDoesNotThrowException(callStartOp);

Đẹp và gọn gàng hả?

My AssertThrowsException()AssertDoesNotThrowException()các phương thức được định nghĩa trên một lớp cơ sở chung như sau:

protected delegate void ExceptionThrower();

/// <summary>
/// Asserts that calling a method results in an exception of the stated type with the stated message.
/// </summary>
/// <param name="exceptionThrowingFunc">Delegate that calls the method to be tested.</param>
/// <param name="expectedExceptionType">The expected type of the exception, e.g. typeof(FormatException).</param>
/// <param name="expectedExceptionMessage">The expected exception message (or fragment of the whole message)</param>
protected void AssertThrowsException(ExceptionThrower exceptionThrowingFunc, Type expectedExceptionType, string expectedExceptionMessage)
{
    try
    {
        exceptionThrowingFunc();
        Assert.Fail("Call did not raise any exception, but one was expected.");
    }
    catch (NUnit.Framework.AssertionException)
    {
        // Ignore and rethrow NUnit exception
        throw;
    }
    catch (Exception ex)
    {
        Assert.IsInstanceOfType(expectedExceptionType, ex, "Exception raised was not the expected type.");
        Assert.IsTrue(ex.Message.Contains(expectedExceptionMessage), "Exception raised did not contain expected message. Expected=\"" + expectedExceptionMessage + "\", got \"" + ex.Message + "\"");
    }
}

/// <summary>
/// Asserts that calling a method does not throw an exception.
/// </summary>
/// <remarks>
/// This is typically only used in conjunction with <see cref="AssertThrowsException"/>. (e.g. once you have tested that an ExceptionThrower
/// method throws an exception then your test may fix the cause of the exception and then call this to make sure it is now fixed).
/// </remarks>
/// <param name="exceptionThrowingFunc">Delegate that calls the method to be tested.</param>
protected void AssertDoesNotThrowException(ExceptionThrower exceptionThrowingFunc)
{
    try
    {
        exceptionThrowingFunc();
    }
    catch (NUnit.Framework.AssertionException)
    {
        // Ignore and rethrow any NUnit exception
        throw;
    }
    catch (Exception ex)
    {
        Assert.Fail("Call raised an unexpected exception: " + ex.Message);
    }
}

4

Đánh dấu bài kiểm tra bằng Mong đợiExceptionAttribute (đây là thuật ngữ trong NUnit hoặc MSTest; người dùng các khuôn khổ kiểm tra đơn vị khác có thể cần dịch).


Không sử dụngpectExceptionAttribute (lý do được đưa ra trong bài đăng của tôi bên dưới). NUnit có Assert.Throws <YourException> () và đối với MSTest, hãy sử dụng một cái gì đó giống như lớp AssertException của tôi bên dưới.
bytedev

3

Với hầu hết các khuôn khổ thử nghiệm đơn vị .net, bạn có thể đặt thuộc tính [Dự kiến ​​ngoại lệ] vào phương pháp thử nghiệm. Tuy nhiên, điều này không thể cho bạn biết rằng ngoại lệ đã xảy ra vào thời điểm bạn mong đợi. Đó là nơi xunit.net có thể giúp đỡ.

Với xunit, bạn có Assert.Throws, vì vậy bạn có thể làm những việc như sau:

    [Fact]
    public void CantDecrementBasketLineQuantityBelowZero()
    {
        var o = new Basket();
        var p = new Product {Id = 1, NetPrice = 23.45m};
        o.AddProduct(p, 1);
        Assert.Throws<BusinessException>(() => o.SetProductQuantity(p, -3));
    }

[Fact] là xunit tương đương với [TestMethod]


Nếu bạn phải sử dụng MSTest (mà tôi thường bị nhà tuyển dụng buộc phải sử dụng) thì hãy xem câu trả lời của tôi bên dưới.
bytedev 21/02/17

0

Đề xuất sử dụng cú pháp ủy quyền rõ ràng của NUnit .

Ví dụ để thử nghiệm ArgumentNullExeption:

[Test]
[TestCase(null)]
public void FooCalculation_InvalidInput_ShouldThrowArgumentNullExeption(string text)
{
    var foo = new Foo();
    Assert.That(() => foo.Calculate(text), Throws.ArgumentNullExeption);

    //Or:
    Assert.That(() => foo.Calculate(text), Throws.Exception.TypeOf<ArgumentNullExeption>);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.