Pytanie Najlepszym sposobem na sprawdzenie wyjątków w Assert, aby upewnić się, że zostaną rzucone


Czy uważasz, że jest to dobry sposób na testowanie wyjątków? Jakieś sugestie?

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

Assert.IsNotNull(exception);

Używam testu MS.


76
2018-04-12 00:12


pochodzenie




Odpowiedzi:


Mam kilka różnych wzorów, z których korzystam. używam ExpectedException atrybut większość czasu, gdy oczekuje się wyjątku. W większości przypadków jest to wystarczające, ale w niektórych przypadkach jest to niewystarczające. Wyjątek może nie być możliwy do wykrycia - ponieważ jest on wywoływany przez metodę, która jest wywoływana przez odbicie - lub może po prostu chcę sprawdzić, czy inne warunki utrzymują się, np. Transakcja została wycofana lub jeszcze ustawiono jakąś wartość. W takich przypadkach pakuję je w try/catch blok, który oczekuje dokładnego wyjątku, robi Assert.Fail jeśli kod się powiedzie, a także przechwyci ogólne wyjątki, aby upewnić się, że nie zostanie zgłoszony inny wyjątek.

Pierwszy przypadek:

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

Drugi przypadek:

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

118
2018-04-12 00:40



Wiele platform testowania jednostkowego implementuje błędy asercji jako wyjątki. Tak więc Assert.Fail () w drugim przypadku zostanie przechwycony przez blok catch (Exception), który ukryje komunikat o wyjątku. Musisz dodać catch (NUnit.Framework.AssertionException) {throw;} lub podobny - zobacz moją odpowiedź. - GrahamS
@ Graham - pisałem to z mojej głowy. Zwykle wydrukowałbym również komunikat wyjątku oprócz jego typu. Chodzi o to, że test zakończyłby się niepowodzeniem, ponieważ drugi program obsługi przechwyciłby błąd asercji i "refail" z informacją o błędzie. - tvanfosson
Chociaż Twój kod jest funkcjonalny, nie polecam używania atrybutu ExpectedException (ponieważ jest zbyt ograniczony i podatny na błędy) lub napisać blok try / catch w każdym teście (ponieważ jest on zbyt skomplikowany i podatny na błędy). Użyj dobrze zaprojektowanej metody asercji - dostarczonej przez strukturę testową lub napisz własną. Możesz uzyskać lepszy kod i nie będziesz musiał wybierać między różnymi technikami lub zmieniać się z jednego na drugiego, gdy test się zmieni. Widzieć stackoverflow.com/a/25084462/2166177 - steve
FYI - przeszedłem na używanie xUnit, który ma silnie wpisane Assert.Throws metoda obejmująca oba te przypadki. - tvanfosson
Atrybut ExpectedException to nieprzyjemny i przestarzały sposób sprawdzania, czy wyjątki są zgłaszane. Zobacz moją pełną odpowiedź poniżej. - bytedev


Jestem tu nowy i nie mam reputacji, aby komentować lub odrzucać, ale chciałem wskazać na błąd w przykładzie Odpowiedź Andy'ego White'a:

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

We wszystkich strukturach testów jednostkowych, które znam, Assert.Fail działa poprzez zgłoszenie wyjątku, więc ogólny haczyk będzie maskował niepowodzenie testu. Gdyby SomethingThatCausesAnException() nie rzuca, Assert.Fail będzie, ale to nigdy nie wypłynie na biegacza testowego, aby wskazać awarię.

Jeśli chcesz uchwycić oczekiwany wyjątek (tj. Potwierdzić pewne szczegóły, takie jak komunikat / właściwości w wyjątku), ważne jest uchwycenie określonego oczekiwanego typu, a nie podstawowej klasy wyjątków. To pozwoliłoby Assert.Fail wyjątek do wypuszczenia bańki (zakładając, że nie rzucasz tego samego typu wyjątku, co struktura testów jednostkowych), ale nadal zezwalaj na sprawdzanie poprawności wyjątku, który został zgłoszony przez twój SomethingThatCausesAnException() metoda.


16
2018-04-12 19:01





Teraz, 2017, możesz to zrobić łatwiej dzięki nowemu MSTest V2 Framework:

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

12
2018-05-07 00:19





Od v 2,5, NUnit ma następujący poziom metody Asserts do testowania wyjątków:

Assert.Throws, który przetestuje pod kątem dokładnego typu wyjątku:

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

I Assert.Catch, który przetestuje pod kątem wyjątku określonego typu lub typu wyjątku pochodnego od tego typu:

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

Na marginesie, podczas debugowania testów jednostkowych, które wyrzucają wyjątki, możesz chcieć zapobiec VS zerwanie z wyjątkiem.

Edytować

Wystarczy podać przykład komentarza Mateusza poniżej, powrót generycznego Assert.Throws i Assert.Catch jest wyjątkiem z typem wyjątku, który można następnie zbadać w celu dalszej inspekcji:

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

11
2017-10-25 11:33



Roy Osherove zaleca to w The Art of Unit Testing, drugi ed, rozdział 2.6.2. - Avi
lubię Assert.Throwsdodatkowo zwraca wyjątek, abyś mógł napisać dalsze asercje na samym wyjściu. - Matthew
Pytanie dotyczyło MSTest, a nie NUnit. - bytedev
Oryginalne pytanie @nashwan OP nie miało tej kwalifikacji, a oznaczanie nadal nie kwalifikuje MS-Test. W obecnej formie jest to C #, .Net, pytanie o testowanie jednostki. - StuartLC


Niestety MSTest STILL ma tylko atrybut ExpectedException (pokazuje tylko, ile MS dba o MSTest), który IMO jest dość okropny, ponieważ łamie wzór Arrange / Act / Assert i nie pozwala dokładnie określić, która linia kodu oczekuje wyjątku wystąpić na.

Kiedy używam (/ wymuszony przez klienta) do używania MSTestu, zawsze używam tej klasy pomocnika:

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

Przykład użycia:

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

10
2017-10-03 14:10





Jako alternatywa do używania ExpectedExceptionatrybut, czasami zdefiniuję dwie pomocne metody dla moich klas testowych:

AssertThrowsException() przyjmuje delegata i zapewnia, że ​​zgłasza oczekiwany wyjątek z oczekiwanym komunikatem.

AssertDoesNotThrowException() przyjmuje tego samego delegata i zapewnia, że ​​nie rzuca wyjątku.

Powiązanie to może być bardzo przydatne, gdy chcesz przetestować wyjątek zgłoszony w jednym przypadku, ale nie drugi.

Używanie ich kodu testu jednostki może wyglądać tak:

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

Fajnie i schludnie?

Mój AssertThrowsException() i AssertDoesNotThrowException() metody są zdefiniowane na wspólnej klasie bazowej w następujący sposób:

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

9
2018-04-12 18:51





Oznacz test za pomocą ExpectedExceptionAttribute (jest to termin w NUnit lub MSTest, użytkownicy innych szkieletów testów jednostkowych mogą potrzebować tłumaczenia).


4
2018-04-12 00:17



Nie używaj ExpectedExceptionAttribute (powód podany w moim wpisie poniżej). NUnit ma Assert.Throws <YourException> () i dla MSTest używają czegoś takiego jak moja klasa AssertException poniżej. - bytedev


W przypadku większości frameworków testujących jednostki .net można umieścić atrybut [ExpectedException] w metodzie testowej. Jednak nie można powiedzieć, że wyjątek zdarzył się w miejscu, w którym oczekiwałeś. To tam gdzie xunit.net może pomóc.

Z xunit masz Assert.Throws, więc możesz robić takie rzeczy:

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

[Fakt] jest odpowiednikiem xunit [TestMethod]


2
2018-04-12 00:17



Jeśli musisz użyć MSTest (którego często jestem zmuszony przez pracodawców), zobacz moją odpowiedź poniżej. - bytedev