Bagaimana cara saya menggunakan
Assert
(atau kelas Tes lainnya?) Untuk memverifikasi bahwa pengecualian telah dilemparkan?
Bagaimana cara saya menggunakan
Assert
(atau kelas Tes lainnya?) Untuk memverifikasi bahwa pengecualian telah dilemparkan?
Jawaban:
Untuk "Tes Tim Visual Studio" tampaknya Anda menerapkan atribut ExpectedException ke metode pengujian.
Contoh dari dokumentasi di sini: Panduan Pengujian Unit dengan Tes Tim Visual Studio
[TestMethod]
[ExpectedException(typeof(ArgumentException),
"A userId of null was inappropriately allowed.")]
public void NullUserIdInConstructor()
{
LogonInfo logonInfo = new LogonInfo(null, "P@ss0word");
}
Biasanya kerangka pengujian Anda akan memiliki jawaban untuk ini. Tetapi jika itu tidak cukup fleksibel, Anda selalu dapat melakukan ini:
try {
somethingThatShouldThrowAnException();
Assert.Fail(); // If it gets to this line, no exception was thrown
} catch (GoodException) { }
Seperti yang ditunjukkan oleh @Jonas, ini TIDAK berfungsi untuk menangkap basis Pengecualian:
try {
somethingThatShouldThrowAnException();
Assert.Fail(); // raises AssertionException
} catch (Exception) {
// Catches the assertion exception, and the test passes
}
Jika Anda benar-benar harus menangkap Pengecualian, Anda harus memikirkan kembali Assert.Fail (). Tapi sungguh, ini pertanda kamu tidak boleh menulis ini dengan tangan; periksa kerangka pengujian Anda untuk opsi, atau lihat apakah Anda dapat melemparkan pengecualian yang lebih bermakna untuk diuji.
catch (AssertionException) { throw; }
Anda harus dapat menyesuaikan pendekatan ini dengan apa pun yang Anda suka - termasuk menentukan jenis pengecualian apa yang harus ditangkap. Jika Anda hanya mengharapkan tipe tertentu, selesaikan catch
blok dengan:
} catch (GoodException) {
} catch (Exception) {
// not the right kind of exception
Assert.Fail();
}
Metode pilihan saya untuk mengimplementasikan ini adalah menulis metode yang disebut Throws, dan menggunakannya sama seperti metode Assert lainnya. Sayangnya, .NET tidak memungkinkan Anda untuk menulis metode ekstensi statis, jadi Anda tidak dapat menggunakan metode ini seolah-olah itu milik build di kelas Assert; buat saja yang disebut MyAssert atau yang serupa. Kelasnya terlihat seperti ini:
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))
);
}
}
}
}
Itu berarti unit test Anda terlihat seperti ini:
[TestMethod()]
public void ExceptionTest()
{
String testStr = null;
MyAssert.Throws<NullReferenceException>(() => testStr.ToUpper());
}
Yang terlihat dan berperilaku jauh lebih seperti sintaks tes unit Anda.
Assert.ThrowsException<T>
dan Assert.ThrowsExceptionAsync<T>
- melihat blogs.msdn.microsoft.com/visualstudioalm/2017/02/25/...
jika Anda menggunakan NUNIT, Anda dapat melakukan sesuatu seperti ini:
Assert.Throws<ExpectedException>(() => methodToTest());
Dimungkinkan juga untuk menyimpan pengecualian yang dilemparkan untuk memvalidasi lebih lanjut:
ExpectedException ex = Assert.Throws<ExpectedException>(() => methodToTest());
Assert.AreEqual( "Expected message text.", ex.Message );
Assert.AreEqual( 5, ex.SomeNumber);
Jika Anda menggunakan MSTest, yang awalnya tidak memiliki ExpectedException
atribut, Anda bisa melakukan ini:
try
{
SomeExceptionThrowingMethod()
Assert.Fail("no exception thrown");
}
catch (Exception ex)
{
Assert.IsTrue(ex is SpecificExceptionType);
}
Berhati-hatilah dalam menggunakan ExpectedException, karena dapat menyebabkan beberapa perangkap seperti yang ditunjukkan di sini:
http://geekswithblogs.net/sdorman/archive/2009/01/17/unit-testing-and-expected-exceptions.aspx
Dan di sini:
http://xunit.github.io/docs/comparisons.html
Jika Anda perlu menguji pengecualian, ada beberapa cara yang tidak disukai. Anda dapat menggunakan metode coba {act / fail} catch {assert}, yang dapat berguna untuk kerangka kerja yang tidak memiliki dukungan langsung untuk tes pengecualian selain ExpectedException.
Alternatif yang lebih baik adalah dengan menggunakan xUnit.NET, yang merupakan kerangka pengujian unit yang sangat modern, berwawasan ke depan, dan dapat diperluas yang telah belajar dari semua kesalahan yang lain, dan ditingkatkan. Salah satu peningkatan tersebut adalah Assert.Throws, yang menyediakan sintaks yang jauh lebih baik untuk menyatakan pengecualian.
Anda dapat menemukan xUnit.NET di github: http://xunit.github.io/
MSTest (v2) sekarang memiliki fungsi Assert.ThrowsException yang dapat digunakan seperti ini:
Assert.ThrowsException<System.FormatException>(() =>
{
Story actual = PersonalSite.Services.Content.ExtractHeader(String.Empty);
});
Anda dapat menginstalnya dengan nuget: Install-Package MSTest.TestFramework
Dalam proyek saya sedang mengerjakan kami punya solusi lain melakukan ini.
Pertama, saya tidak suka ExpectedExceptionAttribute karena tidak mempertimbangkan metode panggilan mana yang menyebabkan Exception.
Saya melakukan ini dengan metode helpermet sebagai gantinya.
Uji
[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());
}
Metode Helper
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;
}
Rapi, bukan;)
Ini adalah atribut pada metode pengujian ... Anda tidak menggunakan Assert. Terlihat seperti ini:
[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()
Anda dapat mengunduh paket dari Nuget menggunakan: PM> Instal-Paket MSTestExtensions yang menambahkan Assert.Throws () sintaks dalam gaya nUnit / xUnit ke MsTest.
Instruksi tingkat tinggi: unduh perakitan dan mewarisi dari BaseTest dan Anda dapat menggunakan Assert.Throws () sintaks .
Metode utama untuk implementasi Throws terlihat sebagai berikut:
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)));
}
}
Pengungkapan: Saya mengumpulkan paket ini.
Info Lebih Lanjut: http://www.bradoncode.com/blog/2012/01/asserting-exceptions-in-mstest-with.html
Anda dapat mencapai ini dengan satu garis sederhana.
Jika operasi Anda foo.bar()
async:
await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());
Jika foo.bar()
tidak async
Assert.ThrowsException<Exception>(() => foo.bar());
ArgumentException
misalnya. Try Catch yang lama dan uji respon pengecualian masih lebih disukai jika Anda memiliki kriteria canggih untuk diuji, tetapi untuk banyak kasus saya, ini sangat membantu!
Saya tidak merekomendasikan menggunakan atribut ExpectedException (karena terlalu membatasi dan rawan kesalahan) atau untuk menulis blok try / catch di setiap tes (karena terlalu rumit dan rawan kesalahan). Gunakan metode penegasan yang dirancang dengan baik - baik yang disediakan oleh kerangka pengujian Anda atau tulis sendiri. Inilah yang saya tulis dan gunakan.
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));
}
}
Contoh menggunakan:
[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)
}
CATATAN
Mengembalikan pengecualian alih-alih mendukung validasi panggilan balik adalah ide yang masuk akal kecuali bahwa melakukan hal itu membuat sintaks panggilan pernyataan ini sangat berbeda dari pernyataan lain yang saya gunakan.
Tidak seperti yang lain, saya menggunakan 'propagasi' bukan 'melempar' karena kami hanya dapat menguji apakah pengecualian merambat dari panggilan. Kami tidak dapat menguji secara langsung bahwa pengecualian dilemparkan. Tapi saya kira Anda bisa membayangkan melempar artinya: dibuang dan tidak tertangkap.
PIKIRAN FINAL
Sebelum beralih ke pendekatan semacam ini, saya mempertimbangkan untuk menggunakan atribut ExpectedException ketika tes hanya memverifikasi tipe pengecualian dan menggunakan blok coba / tangkap jika diperlukan lebih banyak validasi. Tetapi, saya tidak hanya harus memikirkan teknik mana yang akan digunakan untuk setiap tes, tetapi mengubah kode dari satu teknik ke teknik lainnya karena kebutuhan berubah bukanlah usaha yang sepele. Menggunakan satu pendekatan yang konsisten menghemat upaya mental.
Jadi secara ringkas, pendekatan ini olahraga: kemudahan penggunaan, fleksibilitas dan ketahanan (sulit untuk melakukannya salah).
Helper yang disediakan oleh @Richiban di atas berfungsi dengan baik kecuali itu tidak menangani situasi di mana pengecualian dilemparkan, tetapi bukan tipe yang diharapkan. Berikut alamat yang:
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))
);
}
}
}
}
Karena Anda menyebutkan menggunakan kelas tes lain, opsi yang lebih baik daripada ExpectedException
atribut adalah menggunakan Shoudly 's Should.Throw .
Should.Throw<DivideByZeroException>(() => { MyDivideMethod(1, 0); });
Katakanlah kita memiliki persyaratan bahwa pelanggan harus memiliki alamat untuk membuat pesanan . Jika tidak, CreateOrderForCustomer
metode harus menghasilkan ArgumentException
. Lalu kita bisa menulis:
[TestMethod]
public void NullUserIdInConstructor()
{
var customer = new Customer(name := "Justin", address := null};
Should.Throw<ArgumentException>(() => {
var order = CreateOrderForCustomer(customer) });
}
Ini lebih baik daripada menggunakan ExpectedException
atribut karena kita lebih spesifik tentang apa yang harus membuang kesalahan. Ini membuat persyaratan dalam tes kami lebih jelas dan juga membuat diagnosis lebih mudah ketika tes gagal.
Catatan ada juga Should.ThrowAsync
untuk pengujian metode asinkron.
Dalam pengujian unit bawaan VS jika Anda hanya ingin memverifikasi bahwa "pengecualian" dilemparkan, tetapi Anda tidak tahu jenisnya, Anda dapat menggunakan tangkapan semua:
[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void ThrowExceptionTest()
{
//...
}
Yah saya akan cukup meringkas apa yang orang lain katakan di sini sebelumnya ... Ngomong-ngomong, ini kode yang saya buat sesuai dengan jawaban yang baik :) Yang tersisa hanyalah menyalin dan menggunakan ...
/// <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.");
}
Lihat nUnit Documents untuk contoh tentang:
[ExpectedException( typeof( ArgumentException ) )]
Jika menggunakan NUnit , coba ini:
Assert.That(() =>
{
Your_Method_To_Test();
}, Throws.TypeOf<Your_Specific_Exception>().With.Message.EqualTo("Your_Specific_Message"));
Ada perpustakaan luar biasa bernama NFluent yang mempercepat dan memudahkan cara Anda menulis pernyataan .
Cukup mudah untuk menulis pernyataan untuk melemparkan pengecualian:
[Test]
public void given_when_then()
{
Check.ThatCode(() => MethodToTest())
.Throws<Exception>()
.WithMessage("Process has been failed");
}
Meskipun ini adalah pertanyaan lama, saya ingin menambahkan pemikiran baru ke dalam diskusi. Saya telah memperpanjang Arrange, Act, Assert pattern menjadi Expected, Arrange, Act, Assert. Anda dapat membuat pointer pengecualian yang diharapkan, lalu menegaskan itu ditugaskan. Ini terasa lebih bersih daripada melakukan Asserts Anda di blok tangkap, meninggalkan bagian Act Anda sebagian besar hanya untuk satu baris kode untuk memanggil metode yang sedang diuji. Anda juga tidak perlu Assert.Fail();
atau return
dari banyak titik dalam kode. Pengecualian lain yang dilemparkan akan menyebabkan tes gagal, karena itu tidak akan tertangkap, dan jika pengecualian dari tipe yang Anda harapkan dilemparkan, tetapi itu bukan yang Anda harapkan, Menegaskan terhadap pesan atau properti lain dari pengecualian membantu memastikan tes Anda tidak akan lulus secara tidak sengaja.
[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);
}