Atılmalarını sağlamak için Assert ile istisnaları test etmenin en iyi yolu


99

Bunun istisnaları test etmek için iyi bir yol olduğunu düşünüyor musunuz? Herhangi bir öneri?

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

Assert.IsNotNull(exception);

MS Testi kullanıyorum.

Yanıtlar:


139

Kullandığım birkaç farklı modelim var. Kullandığım ExpectedExceptionbir istisna beklenen durumlarda Özellik çoğu zaman. Çoğu durumda bu yeterlidir, ancak bunun yeterli olmadığı bazı durumlar vardır. İstisna yakalanamayabilir - yansıma tarafından çağrılan bir yöntem tarafından atıldığından - veya belki sadece diğer koşulların geçerli olup olmadığını kontrol etmek istiyorum, diyelim ki bir işlem geri alındı ​​veya hala bir değer ayarlandı. Bu durumlarda, onu try/catchtam istisnayı bekleyen, Assert.Failkod başarılı olursa yapan ve ayrıca farklı bir istisnanın atılmamasını sağlamak için genel istisnaları yakalayan bir bloğa sardım.

İlk durum:

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

İkinci durum:

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

16
Birçok birim testi çerçevesi istisna olarak onaylama hatalarını uygular. Böylece, ikinci durumda Assert.Fail (), istisna mesajını gizleyecek olan catch (Exception) bloğu tarafından yakalanacaktır. Bir catch (NUnit.Framework.AssertionException) {throw;} veya benzeri eklemeniz gerekir - cevabımı görün.
GrahamS

@Graham - Bunu kafamın üstünden yazdım. Normalde, türüne ek olarak istisna mesajını da yazdırırdım. Mesele şu ki, ikinci işleyici onaylama hatasını yakalayacağı ve hata ile ilgili bilgilerle "yeniden başlayacağı" için test başarısız olacaktır.
tvanfosson

1
Kodunuz işlevsel olarak sağlam olsa da, ExpectedException özniteliğini (çok kısıtlayıcı ve hataya açık olduğundan) veya her testte bir dene / yakala bloğu yazmayı (çok karmaşık ve hataya açık olduğundan) önermiyorum. İyi tasarlanmış bir iddia yöntemi kullanın - ya test çerçeveniz tarafından sağlanan ya da kendinizinkini yazın. Daha iyi kod elde edebilirsiniz ve test değiştikçe farklı teknikler arasında seçim yapmak veya birinden diğerine geçmek zorunda kalmayacaksınız. Bkz stackoverflow.com/a/25084462/2166177
Steve

Bilginize - Assert.ThrowsBu durumların her ikisini de kapsayan güçlü bir şekilde yazılmış bir yönteme sahip olan xUnit'i kullanmaya başladım .
tvanfosson

ExpectedException özniteliği, istisnaların atılıp atılmadığını test etmek için kötü ve tarihli bir yoldur. Aşağıda tam cevabıma bakın.
bytedev

45

Şimdi, 2017, yeni MSTest V2 Çerçevesi ile bunu daha kolay yapabilirsiniz :

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

//async version
await Assert.ThrowsExceptionAsync<SomeException>(
  () => myObject.SomeMethodAsync()
);

Bu sadece bir System.Exceptionatılırsa başarılı olur . Gibi herhangi bir başkası System.ArgumentExceptiontesti geçemez.
sschoof

2
Başka bir istisna türü bekliyorsanız, bunu test etmelisiniz ... Örneğinizde şunları yapmalısınız: Assert.ThrowsException <ArgumentException> (() => myClass.MyMethodWithError ());
Icaro Bombonato

2
Unutulmaması gereken önemli bir nokta, kullanımının Assert.ThrowsException<MyException>yalnızca sağlanan istisna türüne karşı test edeceği ve türetilmiş istisna türlerinin hiçbirine göre test edilmeyeceğidir. Benim örnekte, test edilen ise Suboldu Throwbir MyInheritedException(temel sınıfından türetilmiş bir tür MyException), daha sonra test olur başarısız .
Ama

Testinizi genişletmek ve bir istisna türünün yanı sıra türetilmiş türlerini de kabul etmek istiyorsanız, bir Try { SubToTest(); Assert.Fail("...") } Catch (AssertFailedException e) {throw;} Catch (MyException e) {...}. Catch (AssertFailedException e) {throw;}(Allgeek'in yorumu)
Ama

16

Burada yeniyim ve yorum veya olumsuz oy kullanma itibarım yok, ancak Andy White'ın cevabındaki örnekteki bir kusura dikkat çekmek istedim :

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

Aşina olduğum tüm birim testi çerçevelerinde, Assert.Failbir istisna atarak çalışır, bu nedenle genel yakalama, testin başarısızlığını maskeleyecektir. Eğer SomethingThatCausesAnException()atmazsa, Assert.Failirade, ancak bu, başarısızlığı göstermek için test koşucusuna asla fışkırmaz.

Beklenen istisnayı yakalamanız gerekiyorsa (yani, istisnadaki mesaj / özellikler gibi belirli ayrıntıları belirtmek için), temel Exception sınıfını değil, belirli beklenen türü yakalamak önemlidir. Bu, Assert.Failistisnanın ortaya çıkmasına izin verir (birim test çerçevenizin yaptığı aynı türdeki istisnayı atmadığınızı varsayarak), ancak yine de SomethingThatCausesAnException()yönteminiz tarafından oluşturulan istisnanın doğrulanmasına izin verir .


16

V 2.5'ten itibaren, NUnitAssert istisnaları test etmek için aşağıdaki yöntem düzeyinde s sahiptir:

Kesin bir istisna türünü test edecek Assert.Throws :

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

Ve Assert.Catchbelirli bir türdeki bir istisnayı veya bu türden türetilen bir istisna türünü test edecek:

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

İstisnalar atmak birim testleri ayıklarken bir kenara,, içinden VS engellemek isteyebilirsiniz istisna üzerine kırma .

Düzenle

Aşağıda Matthew'un yorumuna bir örnek vermek gerekirse, jenerik iadesi Assert.Throwsve Assert.Catchdaha sonra daha fazla inceleme için inceleyebileceğiniz istisna türünün istisnası:

// The type of ex is that of the generic type parameter (SqlException)
var ex = Assert.Throws<SqlException>(() => MethodWhichDeadlocks());
Assert.AreEqual(1205, ex.Number);

2
Roy Osherove, The Art of Unit Testing, ikinci baskı, bölüm 2.6.2'de bunu önermektedir.
Avi

2
Sevdiğim Assert.Throwssen istisna kendisinde başka iddialarını yazabilir bu yüzden özel durum döndürür eklenmesi halinde,.
Matthew

Soru MSTest içindi, NUnit değil.
bytedev

@nashwan OP'nin orijinal sorusu bu niteliğe sahip değildi ve etiketleme hala MS-Test için geçerli değil. Haliyle, bir C #, .Net, Birim Testi sorusudur.
StuartLC

11

Maalesef MSTest STILL yalnızca ExpectedException özniteliğine sahiptir (sadece MS'in MSTest'i ne kadar önemsediğini gösterir) hangi IMO oldukça kötüdür çünkü Düzenleme / Hareket Etme / Onaylama modelini bozar ve istisnayı beklediğiniz kod satırını tam olarak belirlemenize izin vermez meydana gelmek.

MSTest kullanmak için kullandığımda (/ istemci tarafından zorlandığımda) her zaman bu yardımcı sınıfı kullanırım:

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

Kullanım örneği:

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

10

ExpectedExceptionÖzniteliği kullanmaya alternatif olarak , bazen test sınıflarım için iki yararlı yöntem tanımlıyorum:

AssertThrowsException() bir temsilci alır ve beklenen mesajla birlikte beklenen istisnayı attığını ileri sürer.

AssertDoesNotThrowException() aynı temsilciyi alır ve bir istisna oluşturmadığını iddia eder.

Bu eşleştirme, bir durumda bir istisnanın atıldığını, ancak diğerinin atılmadığını test etmek istediğinizde çok yararlı olabilir.

Bunları kullanarak birim test kodum şöyle görünebilir:

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

Güzel ve düzgün ha?

Benim AssertThrowsException()ve AssertDoesNotThrowException()yöntemlerim aşağıdaki gibi ortak bir temel sınıfta tanımlanır:

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

4

Çoğu .net birim testi çerçevesiyle, test yöntemine bir [ExpectedException] niteliği koyabilirsiniz. Ancak bu, istisnanın beklediğiniz noktada gerçekleştiğini söyleyemez. Xunit.net'in yardımcı olabileceği yer burasıdır .

Xunit ile Assert.Throws'a sahipsiniz, böylece aşağıdaki gibi şeyler yapabilirsiniz:

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

[Gerçek], [TestMethod] 'un xunit eşdeğeridir


MSTest'i kullanmanız gerekiyorsa (işverenler tarafından sık sık mecbur bırakıyorum), aşağıdaki cevabıma bakın.
bytedev

4

Testi ExpectedExceptionAttribute ile işaretleyin (bu, NUnit veya MSTest'teki terimdir; diğer birim test çerçevelerinin kullanıcılarının çevirmesi gerekebilir).


ExpectedExceptionAttribute kullanmayın (aşağıdaki yazımda belirtilen neden). NUnit'te Assert.Throws <YourException> () vardır ve MSTest için aşağıdaki AssertException sınıfım gibi bir şey kullanın.
bytedev

0

NUnit'in temiz temsilci sözdizimini kullanmayı önerin .

Test örneği 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>);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.