ฉันจะใช้
Assert
(หรือคลาสทดสอบอื่น ๆ ได้อย่างไร) เพื่อตรวจสอบว่ามีข้อผิดพลาดเกิดขึ้นหรือไม่
ฉันจะใช้
Assert
(หรือคลาสทดสอบอื่น ๆ ได้อย่างไร) เพื่อตรวจสอบว่ามีข้อผิดพลาดเกิดขึ้นหรือไม่
คำตอบ:
สำหรับ "Visual Studio Team Test" จะปรากฏว่าคุณใช้แอตทริบิวต์ ExpectedException กับวิธีการทดสอบ
ตัวอย่างจากเอกสารประกอบที่นี่: บททดสอบหน่วยกับการทดสอบทีม 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
}
หากคุณต้องได้รับการยกเว้นคุณต้องทำการยืนยัน Assert.Fail () อีกครั้ง แต่นี่เป็นสัญญาณที่คุณไม่ควรเขียนด้วยมือ ตรวจสอบกรอบการทดสอบของคุณเพื่อดูตัวเลือกหรือดูว่าคุณสามารถใช้ข้อยกเว้นที่มีความหมายเพื่อทดสอบได้หรือไม่
catch (AssertionException) { throw; }
คุณควรปรับวิธีการนี้ให้เข้ากับสิ่งที่คุณต้องการรวมถึงการระบุว่ามีข้อยกเว้นประเภทใดบ้าง หากคุณคาดหวังเพียงบางประเภทเท่านั้นให้catch
ปิดบล็อกโดยใช้:
} catch (GoodException) {
} catch (Exception) {
// not the right kind of exception
Assert.Fail();
}
วิธีที่ฉันชอบสำหรับการใช้งานนี้คือการเขียนวิธีที่เรียกว่า Throws และใช้วิธีเดียวกับ Assert วิธีอื่น ๆ น่าเสียดายที่. NET ไม่อนุญาตให้คุณเขียนวิธีการขยายแบบคงที่ดังนั้นคุณจึงไม่สามารถใช้วิธีนี้ราวกับว่ามันเป็นของ build ในคลาส Assert แค่สร้างอีกอันที่เรียกว่า 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());
}
รูปลักษณ์และพฤติกรรมใดที่เหมือนส่วนที่เหลือของไวยากรณ์ทดสอบหน่วยของคุณ
Assert.ThrowsException<T>
และAssert.ThrowsExceptionAsync<T>
- ดูblogs.msdn.microsoft.com/visualstudioalm/2017/02/25/…
ถ้าคุณใช้ NUNIT คุณสามารถทำสิ่งนี้:
Assert.Throws<ExpectedException>(() => methodToTest());
นอกจากนี้ยังเป็นไปได้ที่จะเก็บข้อยกเว้นที่ส่งออกไปเพื่อตรวจสอบความถูกต้องเพิ่มเติม:
ExpectedException ex = Assert.Throws<ExpectedException>(() => methodToTest());
Assert.AreEqual( "Expected message text.", ex.Message );
Assert.AreEqual( 5, ex.SomeNumber);
หากคุณกำลังใช้ MSTest ซึ่งเดิมไม่มีExpectedException
แอตทริบิวต์คุณสามารถทำสิ่งนี้ได้:
try
{
SomeExceptionThrowingMethod()
Assert.Fail("no exception thrown");
}
catch (Exception ex)
{
Assert.IsTrue(ex is SpecificExceptionType);
}
ระวังการใช้ ExpectedException เนื่องจากอาจนำไปสู่ข้อผิดพลาดหลายประการดังที่แสดงไว้ที่นี่:
http://geekswithblogs.net/sdorman/archive/2009/01/17/unit-testing-and-expected-exceptions.aspx
และที่นี่:
http://xunit.github.io/docs/comparisons.html
หากคุณต้องการทดสอบข้อยกเว้นมีวิธีการขมวดคิ้วน้อยกว่า คุณสามารถใช้วิธีลอง {act / fail} catch {assert} ซึ่งสามารถเป็นประโยชน์สำหรับเฟรมเวิร์กที่ไม่ได้รับการสนับสนุนโดยตรงสำหรับการทดสอบข้อยกเว้นอื่น ๆ นอกเหนือจาก ExpectedException
ทางเลือกที่ดีกว่าคือการใช้ xUnit.NET ซึ่งเป็นกรอบการทดสอบที่ทันสมัยมองไปข้างหน้าและขยายขอบเขตการทดสอบหน่วยที่เรียนรู้จากข้อผิดพลาดอื่น ๆ ทั้งหมดและปรับปรุง หนึ่งในการปรับปรุงดังกล่าวคือ Assert.Throws ซึ่งมีไวยากรณ์ที่ดีกว่าสำหรับการยืนยันข้อยกเว้น
คุณสามารถค้นหา xUnit.NET ได้ที่ github: http://xunit.github.io/
MSTest (v2) ตอนนี้มีฟังก์ชั่น Assert.ThrowsException ซึ่งสามารถใช้ดังนี้:
Assert.ThrowsException<System.FormatException>(() =>
{
Story actual = PersonalSite.Services.Content.ExtractHeader(String.Empty);
});
คุณสามารถติดตั้งได้ด้วย nuget: Install-Package MSTest.TestFramework
ในโครงการฉันกำลังทำงานกับเรามีวิธีแก้ไขปัญหาอื่นที่ทำเช่นนี้
ก่อนอื่นฉันไม่ชอบ ExpectedExceptionAttribute เพราะมันต้องคำนึงถึงว่าการเรียกเมธอดใดที่ทำให้เกิดข้อยกเว้น
ฉันทำสิ่งนี้ด้วยวิธีช่วยเหลือแทน
ทดสอบ
[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;
}
เรียบร้อยไม่ได้;)
มันเป็นคุณสมบัติของวิธีการทดสอบ ... คุณไม่ได้ใช้ Assert ดูเหมือนว่านี้:
[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()
คุณสามารถดาวน์โหลดแพ็คเกจจาก Nuget ได้โดยใช้: PM> Install-Package MSTestExtensionsที่เพิ่มAssert.Throws ()ไวยากรณ์ในรูปแบบของ nUnit / xUnit ไปยัง MsTest
คำแนะนำระดับสูง: ดาวน์โหลดแอสเซมบลีและสืบทอดจากBaseTestและคุณสามารถใช้ไวยากรณ์Assert.Throws ()
วิธีการหลักสำหรับการใช้งาน 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/blog/2012/01/asserting-exceptions-in-mstest-with.html
คุณสามารถทำสิ่งนี้ได้ง่ายๆด้วยบรรทัดเดียว
หากการดำเนินการของคุณfoo.bar()
เป็นแบบซิงค์:
await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());
หากfoo.bar()
ไม่ใช่แบบอะซิงก์
Assert.ThrowsException<Exception>(() => foo.bar());
ArgumentException
อินสแตนซ์ ลองจับแบบเก่าและทดสอบการตอบสนองข้อยกเว้นยังคงเป็นที่ต้องการหากคุณมีเกณฑ์ขั้นสูงในการทดสอบ แต่สำหรับหลาย ๆ กรณีของฉันสิ่งนี้ช่วยได้มาก!
ฉันไม่แนะนำให้ใช้แอตทริบิวต์ ExpectedException (เนื่องจากมีข้อ จำกัด และข้อผิดพลาดง่ายเกินไป) หรือเขียนบล็อกลอง / 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)
}
หมายเหตุ
การส่งคืนข้อยกเว้นแทนการสนับสนุนการเรียกกลับการตรวจสอบความถูกต้องเป็นความคิดที่สมเหตุสมผลยกเว้นว่าการทำเช่นนั้นจะทำให้ไวยากรณ์การโทรของการยืนยันนี้แตกต่างจากการยืนยันอื่น ๆ ที่ฉันใช้
แตกต่างจากคนอื่น ๆ ฉันใช้ 'เผยแพร่' แทน 'พ่น' เนื่องจากเราสามารถทดสอบได้ว่าข้อยกเว้นแพร่กระจายจากการโทรหรือไม่ เราไม่สามารถทดสอบได้โดยตรงว่ามีข้อผิดพลาดเกิดขึ้น แต่ฉันคิดว่าคุณสามารถโยนภาพเพื่อหมายถึง: โยนและไม่จับ
ความคิดสุดท้าย
ก่อนที่จะเปลี่ยนไปใช้วิธีการแบบนี้ฉันจะพิจารณาใช้แอตทริบิวต์ ExpectedException เมื่อการทดสอบตรวจสอบประเภทข้อยกเว้นเท่านั้นและการใช้บล็อกลอง / catch หากต้องการการตรวจสอบเพิ่มเติม แต่ไม่เพียงแค่ฉันต้องคิดว่าเทคนิคใดที่จะใช้สำหรับการทดสอบแต่ละครั้ง แต่การเปลี่ยนรหัสจากเทคนิคหนึ่งไปอีกเทคนิคหนึ่งเนื่องจากความต้องการที่เปลี่ยนไปนั้นไม่ใช่ความพยายามเล็กน้อย การใช้วิธีการที่สอดคล้องกันอย่างใดอย่างหนึ่งจะช่วยประหยัดความพยายามทางจิต
ดังนั้นโดยสรุปวิธีการนี้จึงเป็นกีฬา: ใช้งานง่ายยืดหยุ่นและทนทาน (ยากที่จะทำผิด)
ผู้ช่วยเหลือที่ได้รับจาก @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 's Should.Throw
Should.Throw<DivideByZeroException>(() => { MyDivideMethod(1, 0); });
สมมติว่าเรามีความต้องการที่ลูกค้าจะต้องมีอยู่ในการสร้างการสั่งซื้อ ถ้าไม่วิธีการควรจะส่งผลในการCreateOrderForCustomer
ArgumentException
จากนั้นเราสามารถเขียน:
[TestMethod]
public void NullUserIdInConstructor()
{
var customer = new Customer(name := "Justin", address := null};
Should.Throw<ArgumentException>(() => {
var order = CreateOrderForCustomer(customer) });
}
สิ่งนี้ดีกว่าการใช้ExpectedException
แอตทริบิวต์เนื่องจากเรามีความเฉพาะเจาะจงเกี่ยวกับสิ่งที่ควรทิ้งข้อผิดพลาด ทำให้ข้อกำหนดในการทดสอบของเราชัดเจนขึ้นและทำให้การวินิจฉัยง่ายขึ้นเมื่อการทดสอบล้มเหลว
หมายเหตุนอกจากนี้ยังมีShould.ThrowAsync
การทดสอบวิธีการแบบอะซิงโครนัส
เป็นอีกทางเลือกหนึ่งที่คุณสามารถลองทดสอบการยกเว้นได้เนื่องจากมี 2 บรรทัดถัดไปในการทดสอบของคุณ
var testDelegate = () => MyService.Method(params);
Assert.Throws<Exception>(testDelegate);
ในการทดสอบหน่วย VS ในตัวถ้าคุณเพียงต้องการตรวจสอบว่า "ข้อยกเว้นใด ๆ " ถูกโยน แต่คุณไม่ทราบประเภทคุณสามารถใช้ catch all:
[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void ThrowExceptionTest()
{
//...
}
ฉันจะสรุปสิ่งที่คนอื่น ๆ ที่นี่พูดก่อนหน้านี้ ... นี่คือรหัสที่ฉันสร้างขึ้นตามคำตอบที่ดี :) สิ่งที่เหลือไว้ก็คือคัดลอกและใช้ ...
/// <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.");
}
ลองดูตัวอย่างของเอกสารเกี่ยวกับ:
[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"));
มีห้องสมุดที่ยอดเยี่ยมที่เรียกว่าNFluentซึ่งเพิ่มความเร็วและลดความยุ่งยากในการเขียนคำยืนยันของคุณ
มันค่อนข้างตรงไปตรงมาที่จะเขียนคำยืนยันในการโยนข้อยกเว้น:
[Test]
public void given_when_then()
{
Check.ThatCode(() => MethodToTest())
.Throws<Exception>()
.WithMessage("Process has been failed");
}
แม้ว่านี่จะเป็นคำถามเก่า แต่ฉันต้องการเพิ่มความคิดใหม่ให้กับการอภิปราย ฉันได้ขยาย Arrange, Act, Assert pattern เพื่อคาดหวัง, Arrange, Act, Assert คุณสามารถสร้างตัวชี้ข้อยกเว้นที่คาดไว้จากนั้นยืนยันว่าได้รับมอบหมายให้ สิ่งนี้ให้ความรู้สึกที่สะอาดกว่าการทำ Asserts ของคุณใน catch catch โดยปล่อยให้ 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);
}