Làm cách nào để sử dụng
Assert
(hoặc lớp Kiểm tra khác?) Để xác minh rằng một ngoại lệ đã bị ném?
Làm cách nào để sử dụng
Assert
(hoặc lớp Kiểm tra khác?) Để xác minh rằng một ngoại lệ đã bị ném?
Câu trả lời:
Đối với "Visual Studio Team Test", có vẻ như bạn áp dụng thuộc tính ExpectedException cho phương thức thử nghiệm.
Ví dụ từ tài liệu ở đây: Hướng dẫn kiểm tra đơn vị với Visual Studio Team Test
[TestMethod]
[ExpectedException(typeof(ArgumentException),
"A userId of null was inappropriately allowed.")]
public void NullUserIdInConstructor()
{
LogonInfo logonInfo = new LogonInfo(null, "P@ss0word");
}
Thông thường khung thử nghiệm của bạn sẽ có câu trả lời cho việc này. Nhưng nếu nó không đủ linh hoạt, bạn luôn có thể làm điều này:
try {
somethingThatShouldThrowAnException();
Assert.Fail(); // If it gets to this line, no exception was thrown
} catch (GoodException) { }
Như @Jonas chỉ ra, điều này KHÔNG hoạt động để bắt Ngoại lệ cơ sở:
try {
somethingThatShouldThrowAnException();
Assert.Fail(); // raises AssertionException
} catch (Exception) {
// Catches the assertion exception, and the test passes
}
Nếu bạn hoàn toàn phải bắt Ngoại lệ, bạn cần phải lấy lại Assert.Fail (). Nhưng thực sự, đây là một dấu hiệu bạn không nên viết tay này; kiểm tra khung kiểm tra của bạn để biết các tùy chọn hoặc xem liệu bạn có thể đưa ra một ngoại lệ có ý nghĩa hơn để kiểm tra hay không.
catch (AssertionException) { throw; }
Bạn sẽ có thể điều chỉnh cách tiếp cận này với bất cứ điều gì bạn thích - bao gồm cả việc chỉ định loại ngoại lệ nào cần nắm bắt. Nếu bạn chỉ mong đợi một số loại nhất định, hãy kết thúc các catch
khối bằng:
} catch (GoodException) {
} catch (Exception) {
// not the right kind of exception
Assert.Fail();
}
Phương pháp ưa thích của tôi để thực hiện điều này là viết một phương thức có tên là Ném và sử dụng nó giống như bất kỳ phương thức Khẳng định nào khác. Thật không may, .NET không cho phép bạn viết một phương thức mở rộng tĩnh, vì vậy bạn không thể sử dụng phương thức này như thể nó thực sự thuộc về bản dựng trong lớp Assert; chỉ cần làm một cái khác gọi là MyAssert hoặc một cái gì đó tương tự. Lớp học trông như thế này:
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))
);
}
}
}
}
Điều đó có nghĩa là bài kiểm tra đơn vị của bạn trông như thế này:
[TestMethod()]
public void ExceptionTest()
{
String testStr = null;
MyAssert.Throws<NullReferenceException>(() => testStr.ToUpper());
}
Mà trông và hành xử giống như nhiều cú pháp kiểm tra đơn vị còn lại của bạn.
Assert.ThrowsException<T>
và Assert.ThrowsExceptionAsync<T>
- xem blogs.msdn.microsoft.com/visualstudioalm/2017/02/25/...
nếu bạn sử dụng NUNIT, bạn có thể làm một cái gì đó như thế này:
Assert.Throws<ExpectedException>(() => methodToTest());
Cũng có thể lưu trữ ngoại lệ bị ném để xác thực nó thêm:
ExpectedException ex = Assert.Throws<ExpectedException>(() => methodToTest());
Assert.AreEqual( "Expected message text.", ex.Message );
Assert.AreEqual( 5, ex.SomeNumber);
Nếu bạn đang sử dụng MSTest, vốn ban đầu không có ExpectedException
thuộc tính, bạn có thể làm điều này:
try
{
SomeExceptionThrowingMethod()
Assert.Fail("no exception thrown");
}
catch (Exception ex)
{
Assert.IsTrue(ex is SpecificExceptionType);
}
Hãy cảnh giác khi sử dụng ExpectedException, vì nó có thể dẫn đến một số cạm bẫy như được trình bày ở đây:
http://geekswithbloss.net/sdorman/archive/2009/01/17/unit-testing-and-azed-exceptions.aspx
Và đây:
http://xunit.github.io/docs/comparisons.html
Nếu bạn cần kiểm tra các trường hợp ngoại lệ, sẽ có ít cách hơn. Bạn có thể sử dụng phương thức try {act / fail} Catch {assert}, có thể hữu ích cho các khung không có hỗ trợ trực tiếp cho các thử nghiệm ngoại lệ khác ngoài ExpectedException.
Một cách khác tốt hơn là sử dụng xUnit.NET, một khung kiểm tra đơn vị rất hiện đại, hướng tới tương lai và có thể mở rộng, đã học được từ tất cả các lỗi khác và được cải thiện. Một cải tiến như vậy là Assert.Throws, cung cấp một cú pháp tốt hơn nhiều để khẳng định các ngoại lệ.
Bạn có thể tìm thấy xUnit.NET tại github: http://xunit.github.io/
MSTest (v2) hiện có chức năng Assert.ThrowsException có thể được sử dụng như thế này:
Assert.ThrowsException<System.FormatException>(() =>
{
Story actual = PersonalSite.Services.Content.ExtractHeader(String.Empty);
});
Bạn có thể cài đặt nó với nuget: Install-Package MSTest.TestFramework
Trong một dự án, tôi làm việc với chúng tôi có một giải pháp khác.
Đầu tiên tôi không dùng như ExpectedExceptionAttribution vì nó cần xem xét phương thức nào gây ra Ngoại lệ.
Tôi làm điều này với một helpermethod thay thế.
Kiểm tra
[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;
}
Gọn gàng, không phải là nó;)
Đây là một thuộc tính trên phương thức thử nghiệm ... bạn không sử dụng Assert. Trông như thế này:
[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()
Bạn có thể tải xuống gói từ Nuget bằng cách sử dụng: PM> Cài đặt MSTestExtensions cài đặt thêm cú pháp Assert.Throws () theo kiểu nUnit / xUnit cho MsTest.
Hướng dẫn cấp cao: tải xuống bản lắp ráp và kế thừa từ BaseTest và bạn có thể sử dụng cú pháp Assert.Throws () .
Phương thức chính để thực hiện Ném như sau:
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)));
}
}
Tiết lộ: Tôi kết hợp gói này.
Thông tin thêm: http://www.bradoncode.com/blog/2012/01/asserting-exceptions-in-mstest-with.html
Bạn có thể đạt được điều này với một dòng đơn giản.
Nếu hoạt động của bạn không đồng bộ foo.bar()
:
await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());
Nếu foo.bar()
không đồng bộ
Assert.ThrowsException<Exception>(() => foo.bar());
ArgumentException
ví dụ. Thử Catch cũ và kiểm tra phản hồi ngoại lệ vẫn được ưu tiên nếu bạn có tiêu chí nâng cao để kiểm tra, nhưng đối với nhiều trường hợp của tôi, điều này giúp ích rất nhiều!
Tôi không khuyên bạn nê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 thử / bắt trong mỗi thử nghiệm (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 - được cung cấp bởi khung kiểm tra của bạn hoặc tự viết. Đây là những gì tôi đã viết và sử dụng.
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));
}
}
Ví dụ sử dụng:
[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)
}
GHI CHÚ
Trả lại ngoại lệ thay vì hỗ trợ gọi lại xác thực là một ý tưởng hợp lý ngoại trừ việc làm như vậy làm cho cú pháp gọi của khẳng định này rất khác so với các xác nhận khác mà tôi sử dụng.
Không giống như những người khác, tôi sử dụng 'tuyên truyền' thay vì 'ném' vì chúng tôi chỉ có thể kiểm tra xem một ngoại lệ có lan truyền từ một cuộc gọi hay không. Chúng tôi không thể kiểm tra trực tiếp rằng một ngoại lệ được ném ra. Nhưng tôi cho rằng bạn có thể ném hình ảnh có nghĩa là: ném và không bị bắt.
CUỐI CÙNG
Trước khi chuyển sang cách tiếp cận này, tôi đã cân nhắc sử dụng thuộc tính ExpectedException khi kiểm tra chỉ xác minh loại ngoại lệ và sử dụng khối thử / bắt nếu cần thêm xác thực. Nhưng, tôi không chỉ phải suy nghĩ về việc sử dụng kỹ thuật nào cho mỗi thử nghiệm, mà việc thay đổi mã từ kỹ thuật này sang kỹ thuật khác vì nhu cầu thay đổi không phải là nỗ lực tầm thường. Sử dụng một phương pháp nhất quán giúp tiết kiệm nỗ lực tinh thần.
Vì vậy, tóm lại, cách tiếp cận này thể thao: dễ sử dụng, linh hoạt và mạnh mẽ (khó làm sai).
Trình trợ giúp được cung cấp bởi @Richiban ở trên hoạt động rất tốt trừ việc nó không xử lý tình huống ném ngoại lệ, nhưng không phải là loại được mong đợi. Các địa chỉ sau:
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))
);
}
}
}
}
Vì bạn đề cập đến việc sử dụng các lớp kiểm tra khác, nên một tùy chọn tốt hơn ExpectedException
thuộc tính là sử dụng Should.Throw của Shoudly .
Should.Throw<DivideByZeroException>(() => { MyDivideMethod(1, 0); });
Giả sử chúng tôi có một yêu cầu là khách hàng phải có địa chỉ để tạo đơn hàng . Nếu không, CreateOrderForCustomer
phương pháp sẽ dẫn đến một ArgumentException
. Sau đó chúng ta có thể viết:
[TestMethod]
public void NullUserIdInConstructor()
{
var customer = new Customer(name := "Justin", address := null};
Should.Throw<ArgumentException>(() => {
var order = CreateOrderForCustomer(customer) });
}
Điều này tốt hơn là sử dụng một ExpectedException
thuộc tính bởi vì chúng tôi đang cụ thể về những gì sẽ gây ra lỗi. Điều này làm cho các yêu cầu trong các xét nghiệm của chúng tôi rõ ràng hơn và cũng giúp chẩn đoán dễ dàng hơn khi thử nghiệm thất bại.
Lưu ý rằng cũng có một Should.ThrowAsync
thử nghiệm phương pháp không đồng bộ.
Chà, tôi sẽ tổng hợp khá nhiều những gì mọi người khác ở đây đã nói trước đây ... Dù sao, đây là đoạn mã tôi xây dựng theo câu trả lời hay :) Tất cả những gì còn lại phải làm là sao chép và sử dụng ...
/// <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.");
}
Kiểm tra nUnit Docs để biết ví dụ về:
[ExpectedException( typeof( ArgumentException ) )]
Trong trường hợp sử dụng NUnit , hãy thử điều này:
Assert.That(() =>
{
Your_Method_To_Test();
}, Throws.TypeOf<Your_Specific_Exception>().With.Message.EqualTo("Your_Specific_Message"));
Có một thư viện tuyệt vời tên là NFluent giúp tăng tốc và giảm bớt cách bạn viết các xác nhận của mình .
Thật đơn giản để viết một khẳng định cho việc ném một ngoại lệ:
[Test]
public void given_when_then()
{
Check.ThatCode(() => MethodToTest())
.Throws<Exception>()
.WithMessage("Process has been failed");
}
Mặc dù đây là một câu hỏi cũ, tôi muốn thêm một ý nghĩ mới vào cuộc thảo luận. Tôi đã mở rộng mô hình Arrange, Act, Assert được mong đợi, Sắp xếp, Act, Assert. Bạn có thể tạo một con trỏ ngoại lệ dự kiến, sau đó xác nhận nó được gán cho. Điều này cảm thấy sạch sẽ hơn so với thực hiện các Xác nhận của bạn trong một khối bắt, để lại phần Act của bạn chủ yếu chỉ cho một dòng mã để gọi phương thức đang thử nghiệm. Bạn cũng không phải Assert.Fail();
hoặc return
từ nhiều điểm trong mã. Bất kỳ ngoại lệ nào khác được ném sẽ khiến bài kiểm tra thất bại, vì nó sẽ không bị bắt và nếu một ngoại lệ của loại dự kiến của bạn bị ném, nhưng đó không phải là trường hợp bạn mong đợi, Khẳng định chống lại tin nhắn hoặc các thuộc tính khác của ngoại lệ giúp đảm bảo bài kiểm tra của bạn sẽ không vô tình vượt qua.
[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);
}