Cara terbaik untuk menguji pengecualian dengan Assert untuk memastikan pengecualian tersebut akan dibuang

97

Menurut Anda, apakah ini cara yang baik untuk menguji pengecualian? Ada saran?

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

Assert.IsNotNull(exception);

Saya menggunakan MS Test.

Hannoun Yassir
sumber

Jawaban:

137

Saya memiliki beberapa pola berbeda yang saya gunakan. Saya menggunakan ExpectedExceptionatribut sebagian besar waktu ketika pengecualian diharapkan. Ini sudah cukup untuk kebanyakan kasus, namun, ada beberapa kasus ketika ini tidak cukup. Pengecualian mungkin tidak dapat ditangkap - karena dilemparkan oleh metode yang dipanggil oleh refleksi - atau mungkin saya hanya ingin memeriksa apakah kondisi lain berlaku, katakanlah transaksi dibatalkan atau beberapa nilai masih ditetapkan. Dalam kasus ini saya membungkusnya dalam try/catchblok yang mengharapkan pengecualian yang tepat, Assert.Failapakah kode berhasil dan juga menangkap pengecualian umum untuk memastikan bahwa pengecualian yang berbeda tidak dilemparkan.

Kasus pertama:

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

Kasus kedua:

[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 )
        );
    }
}
tvanfosson.dll
sumber
16
Banyak kerangka pengujian unit yang mengimplementasikan kegagalan pernyataan sebagai pengecualian. Jadi Assert.Fail () dalam kasus kedua akan ditangkap oleh blok catch (Exception), yang akan menyembunyikan pesan pengecualian. Anda perlu menambahkan tangkapan (NUnit.Framework.AssertionException) {throw;} atau serupa - lihat jawaban saya.
GrahamS
@Graham - Saya mengetik ini dari atas kepala saya. Biasanya saya juga akan mencetak pesan pengecualian selain jenisnya. Intinya adalah bahwa pengujian akan gagal karena penangan kedua akan menangkap kegagalan pernyataan dan "refail" dengan informasi tentang kesalahan tersebut.
tvanfosson
1
Meskipun kode Anda berfungsi dengan baik, saya tidak merekomendasikan penggunaan atribut ExpectedException (karena terlalu membatasi dan rawan kesalahan) atau menulis blok coba / tangkap di setiap pengujian (karena terlalu rumit dan rawan kesalahan). Gunakan metode assert yang dirancang dengan baik - baik yang disediakan oleh framework pengujian Anda atau tulis sendiri. Anda dapat mencapai kode yang lebih baik dan Anda tidak perlu memilih di antara teknik yang berbeda atau mengubah satu sama lain saat pengujian berubah. Lihat stackoverflow.com/a/25084462/2166177
steve
FYI - Saya telah beralih menggunakan xUnit yang memiliki metode yang sangat diketik Assert.Throwsyang mencakup kedua kasus ini.
tvanfosson
Atribut ExpectedException adalah cara yang buruk dan kuno untuk menguji apakah pengecualian dilemparkan. Lihat jawaban lengkap saya di bawah ini.
bytedev
45

Sekarang, 2017, Anda dapat melakukannya lebih mudah dengan Kerangka Kerja MSTest V2 yang baru :

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

//async version
await Assert.ThrowsExceptionAsync<SomeException>(
  () => myObject.SomeMethodAsync()
);
Icaro Bombonato
sumber
Ini hanya akan berhasil jika System.Exceptiondilempar. Yang lainnya, seperti System.ArgumentExceptionakan gagal dalam ujian.
sschoof
2
Jika Anda mengharapkan jenis pengecualian lain, Anda harus mengujinya ... Dalam contoh Anda, Anda harus melakukan: Assert.ThrowsException <ArgumentException> (() => myClass.MyMethodWithError ());
Icaro Bombonato
2
Sesuatu yang penting untuk diperhatikan adalah bahwa penggunaan Assert.ThrowsException<MyException>akan hanya menguji jenis pengecualian yang disediakan, dan bukan jenis pengecualian turunannya. Dalam contoh saya, jika diuji Subadalah untuk Throwsebuah MyInheritedException(tipe turunan dari kelas dasar MyException), maka tes akan gagal .
Ama
Jika Anda ingin memperluas pengujian Anda dan menerima jenis pengecualian serta jenis turunannya, gunakan a Try { SubToTest(); Assert.Fail("...") } Catch (AssertFailedException e) {throw;} Catch (MyException e) {...}. Perhatikan yang paling penting dari Catch (AssertFailedException e) {throw;}(lih. Komentar dari allgeek)
Ama
16

Saya baru di sini dan tidak memiliki reputasi untuk berkomentar atau memberi suara negatif, tetapi ingin menunjukkan kesalahan dalam contoh dalam balasan Andy White :

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

Di semua kerangka kerja pengujian unit yang saya kenal, Assert.Failbekerja dengan memberikan pengecualian, sehingga tangkapan umum sebenarnya akan menutupi kegagalan pengujian. Jika SomethingThatCausesAnException()tidak melempar, Assert.Failkemauan, tapi itu tidak akan pernah meluncur ke runner pengujian untuk menunjukkan kegagalan.

Jika Anda perlu menangkap pengecualian yang diharapkan (yaitu, untuk menegaskan detail tertentu, seperti pesan / properti pada pengecualian), penting untuk menangkap tipe khusus yang diharapkan, dan bukan kelas Exception dasar. Itu akan memungkinkan Assert.Failpengecualian untuk diluncurkan (dengan asumsi Anda tidak melemparkan jenis pengecualian yang sama dengan yang dilakukan oleh kerangka pengujian unit Anda), tetapi masih memungkinkan validasi pada pengecualian yang dilemparkan oleh SomethingThatCausesAnException()metode Anda .

allgeek
sumber
15

Mulai v 2.5, NUnit memiliki tingkat metode berikut Assertuntuk menguji pengecualian:

Assert.Throws , yang akan menguji jenis pengecualian yang tepat:

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

Dan Assert.Catch, yang akan menguji pengecualian dari tipe tertentu, atau tipe pengecualian yang diturunkan dari tipe ini:

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

Selain itu, saat men-debug pengujian unit yang menampilkan pengecualian, Anda mungkin ingin mencegah VS melanggar pengecualian .

Edit

Hanya untuk memberikan contoh komentar Matthew di bawah ini, kembalinya generik Assert.Throwsdan Assert.Catchmerupakan pengecualian dengan jenis pengecualian, yang kemudian dapat Anda periksa untuk pemeriksaan lebih lanjut:

// The type of ex is that of the generic type parameter (SqlException)
var ex = Assert.Throws<SqlException>(() => MethodWhichDeadlocks());
Assert.AreEqual(1205, ex.Number);
StuartLC
sumber
2
Roy Osherove merekomendasikan ini dalam The Art of Unit Testing, edisi kedua, bagian 2.6.2.
Avi
2
Saya suka Assert.Throws, selain itu ia mengembalikan pengecualian sehingga Anda dapat menulis pernyataan lebih lanjut tentang pengecualian itu sendiri.
Matius
Pertanyaannya adalah untuk MSTest bukan NUnit.
bytedev
Pertanyaan asli @nashwan OP tidak memenuhi kualifikasi tersebut, dan pemberian tag masih belum memenuhi syarat MS-Test. Seperti berdiri, ini adalah pertanyaan C #, .Net, Unit-Testing.
StuartLC
11

Sayangnya MSTest MASIH hanya benar-benar memiliki atribut ExpectedException (hanya menunjukkan seberapa besar MS peduli tentang MSTest) yang IMO cukup buruk karena merusak pola Atur / Tindakan / Tegaskan dan tidak memungkinkan Anda untuk menentukan dengan tepat baris kode mana yang Anda harapkan pengecualiannya terjadi.

Ketika saya menggunakan (/ dipaksa oleh klien) untuk menggunakan MSTest, saya selalu menggunakan kelas helper ini:

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.");
    }
}

Contoh penggunaan:

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

Sebagai alternatif untuk menggunakan ExpectedExceptionatribut, terkadang saya mendefinisikan dua metode yang berguna untuk kelas pengujian saya:

AssertThrowsException() mengambil sebuah delegasi dan menegaskan bahwa itu melempar pengecualian yang diharapkan dengan pesan yang diharapkan.

AssertDoesNotThrowException() mengambil delegasi yang sama dan menegaskan bahwa itu tidak memunculkan pengecualian.

Penyandingan ini bisa sangat berguna ketika Anda ingin menguji bahwa pengecualian dilemparkan dalam satu kasus, tetapi tidak di kasus lainnya.

Menggunakannya, kode pengujian unit saya mungkin terlihat seperti ini:

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

Bagus dan rapi ya?

Metode AssertThrowsException()dan saya AssertDoesNotThrowException()didefinisikan pada kelas dasar umum sebagai berikut:

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);
    }
}
GrahamS
sumber
4

Dengan sebagian besar framework pengujian unit .net, Anda dapat menempatkan atribut [ExpectedException] pada metode pengujian. Namun ini tidak dapat memberi tahu Anda bahwa pengecualian terjadi pada titik yang Anda harapkan. Di situlah xunit.net dapat membantu.

Dengan xunit Anda memiliki Assert.Throws, sehingga Anda dapat melakukan hal-hal seperti ini:

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

[Fakta] adalah xunit yang setara dengan [TestMethod]

Steve Willcock
sumber
Jika Anda harus menggunakan MSTest (yang saya sering dipaksa oleh majikan) maka lihat jawaban saya di bawah ini.
bytedev
4

Tandai pengujian dengan ExpectedExceptionAttribute (ini adalah istilah di NUnit atau MSTest; pengguna framework pengujian unit lain mungkin perlu menerjemahkan).

itowlson
sumber
Jangan gunakan ExpectedExceptionAttribute (alasan diberikan dalam posting saya di bawah). NUnit memiliki Assert.Throws <YourException> () dan untuk MSTest gunakan sesuatu seperti kelas AssertException saya di bawah.
bytedev
0

Sarankan menggunakan sintaks delegasi bersih NUnit .

Contoh untuk pengujian 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>);
}
Shahar Shokrani
sumber