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


Bạn có nghĩ rằng đây là một cách tốt để thử nghiệm 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.


76
2018-04-12 00:12


gốc




Các câu trả lời:


Tôi có một vài mẫu khác nhau mà tôi sử dụng. tôi sử dụng ExpectedException thuộc tính phần lớn thời gian khi một ngoại lệ được mong đợi. Đ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 khi đ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 sự phản chiếu - hoặc có lẽ tôi chỉ muốn kiểm tra xem các điều kiện khác có giữ lại hay không, nói rằng một giao dịch được khôi phục hoặc một số giá trị vẫn được thiết lập. Trong những trường hợp này, tôi quấn nó trong một try/catch khối dự kiến ​​ngoại lệ chính xác, thực hiện Assert.Fail nế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 )
        );
    }
}

118
2018-04-12 00:40



Nhiều đơn vị kiểm tra khung thực hiện xác nhận thất bại như trường hợp ngoại lệ. Vì vậy, Assert.Fail () trong trường hợp thứ hai sẽ bị bắt bởi khối catch (Exception), nó sẽ ẩn thông điệp ngoại lệ. Bạn cần phải thêm một catch (NUnit.Framework.AssertionException) {throw;} hoặc tương tự - xem câu trả lời của tôi. - GrahamS
@ Graham - Tôi đã gõ cái này lên đỉnh đầu. Thông thường tôi cũng sẽ in ra thông điệp ngoại lệ ngoài loại của nó. Vấn đề là thử nghiệm sẽ thất bại vì trình xử lý thứ hai sẽ bắt được lỗi xác nhận và "refail" với thông tin về lỗi. - tvanfosson
Mặc dù mã của bạn có chức năng âm thanh nhưng tôi không khuyên bạn sử dụng thuộc tính ExpectedException (vì nó quá hạn chế và dễ bị lỗi) hoặc viết một khối try / catch trong mỗi bài kiểm tra (vì nó quá phức tạp và dễ bị lỗi). Sử dụng một phương pháp khẳng định được thiết kế tốt - hoặc được cung cấp bởi khung kiểm tra của bạn hoặc viết của riêng bạn. 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ừ một đến những thay đổi khác. Xem stackoverflow.com/a/25084462/2166177 - steve
FYI - Tôi đã chuyển sang sử dụng xUnit có kiểu gõ mạnh mẽ Assert.Throws phương pháp bao gồm cả hai trường hợp này. - tvanfosson
ExpectedException thuộc tính là khó chịu và ngày cách để kiểm tra xem các trường hợp ngoại lệ được ném. Xem câu trả lời đầy đủ của tôi bên dưới. - bytedev


Tôi mới ở đây và không có danh tiếng để bình luận hoặc downvote, 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ổ thử nghiệm đơn vị tôi quen thuộc, Assert.Fail hoạt động bằng cách ném một ngoại lệ, do đó, việc nắm 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.Fail sẽ, nhưng điều đó sẽ không bao giờ bong bóng ra để thử nghiệm Á hậu để chỉ ra thất bại.

Nếu bạn cần nắm bắt ngoại lệ được mong đợi (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 về ngoại lệ), điều quan trọng là phải nắm bắt loại mong đợi cụ thể và không phải là lớp Ngoại lệ cơ bản. Điều đó sẽ cho phép Assert.Fail ngoại lệ để bong bóng ra ngoài (giả sử bạn không ném cùng loại ngoại lệ mà khung kiểm thử đơn vị của bạn làm), nhưng vẫn cho phép xác nhận về ngoại lệ được ném bởi SomethingThatCausesAnException() phương pháp.


16
2018-04-12 19:01





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

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

12
2018-05-07 00:19





Từ v 2,5, NUnit có mức phương pháp sau Asserts để kiểm tra ngoại lệ:

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

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

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

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

Là một sang một bên, khi gỡ lỗi đơn vị kiểm tra mà ném ngoại lệ, bạn có thể muốn ngăn chặn VS từ vi phạm ngoại lệ.

Chỉnh sửa

Chỉ để đưa ra một ví dụ về bình luận của Matthew dưới đây, sự trở lại của chung chung Assert.Throws và Assert.Catch là ngoại lệ với loại ngoại lệ mà 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);

11
2017-10-25 11:33



Roy Osherove đề xuất điều này trong Bài kiểm tra Nghệ thuật Đơn vị, phần thứ hai, phần 2.6.2. - Avi
tôi thích Assert.Throws, ngoài ra nó trả về ngoại lệ, do đó bạn có thể viết thêm các xác nhận về ngoại lệ. - Matthew
Câu hỏi đặt ra là MSTest không NUnit. - bytedev
Câu hỏi ban đầu của @nashwan OP không có bằng cấp đó và việc gắn thẻ vẫn không đủ điều kiện tham gia thử nghiệm MS. Khi nó đứng, nó là một câu hỏi C #, .Net, Unit-Testing. - StuartLC


Thật không may MSTest STILL chỉ thực sự có thuộc tính ExpectedException (chỉ hiển thị số lượng MS quan tâm đến MSTest) mà IMO khá khủng khiếp vì nó phá vỡ mẫu Arrange / Act / Assert 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 (/ buộc bởi một khách hàng) để sử dụng MSTest tôi luôn luôn sử dụng lớp helper 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
2017-10-03 14:10





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

AssertThrowsException() có 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() có cùng một đại biểu và khẳng định rằng nó không ném một ngoại lệ.

Ghép nối này có thể rất hữu ích khi bạn muốn thử nghiệm rằng một ngoại lệ được ném trong một trường hợp, nhưng không phải là ngoại lệ.

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

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ả?

Của tôi AssertThrowsException() và 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);
    }
}

9
2018-04-12 18:51





Đánh dấu thử nghiệm với ExpectedExceptionAttribute (đây là thuật ngữ trong NUnit hoặc MSTest; người dùng các khung kiểm thử đơn vị khác có thể cần dịch).


4
2018-04-12 00:17



Không sử dụng ExpectedExceptionAttribute (lý do được đưa ra trong bài viết của tôi dưới đây). NUnit có Assert.Throws <YourException> () và cho MSTest sử dụng một cái gì đó giống như lớp AssertException của tôi dưới đây. - bytedev


Với hầu hết các khung kiểm thử đơn vị .net, bạn có thể đặt thuộc tính [ExpectedException] vào phương thức 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 tại thời điểm bạn mong đợi nó. Đó 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ư thế này:

    [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));
    }

[Sự kiện] là tương đương xunit của [TestMethod]


2
2018-04-12 00:17



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