Jak użyć
Assert
(lub innej klasy Test?), Aby sprawdzić, czy został zgłoszony wyjątek?
Jak użyć
Assert
(lub innej klasy Test?), Aby sprawdzić, czy został zgłoszony wyjątek?
Odpowiedzi:
W przypadku „Visual Studio Team Test” wydaje się, że stosuje się atrybut ExpectedException do metody testu.
Przykład z dokumentacji tutaj: Przewodnik po testach jednostkowych z Visual Studio Team Test
[TestMethod]
[ExpectedException(typeof(ArgumentException),
"A userId of null was inappropriately allowed.")]
public void NullUserIdInConstructor()
{
LogonInfo logonInfo = new LogonInfo(null, "P@ss0word");
}
Zazwyczaj Twoja platforma testowania będzie na to odpowiedź. Ale jeśli nie jest wystarczająco elastyczny, zawsze możesz to zrobić:
try {
somethingThatShouldThrowAnException();
Assert.Fail(); // If it gets to this line, no exception was thrown
} catch (GoodException) { }
Jak zauważa @Jonas, NIE DZIAŁA to w przypadku przechwycenia podstawowego wyjątku:
try {
somethingThatShouldThrowAnException();
Assert.Fail(); // raises AssertionException
} catch (Exception) {
// Catches the assertion exception, and the test passes
}
Jeśli absolutnie musisz złapać wyjątek, musisz ponownie uruchomić Assert.Fail (). Ale tak naprawdę jest to znak, że nie powinieneś pisać tego ręcznie; sprawdź środowisko testowe pod kątem opcji lub sprawdź, czy możesz zgłosić bardziej znaczący wyjątek do przetestowania.
catch (AssertionException) { throw; }
Powinieneś być w stanie dostosować to podejście do tego, co lubisz - w tym określając, jakie wyjątki należy złapać. Jeśli oczekujesz tylko niektórych typów, zakończ catch
bloki:
} catch (GoodException) {
} catch (Exception) {
// not the right kind of exception
Assert.Fail();
}
Moją preferowaną metodą realizacji tego jest napisanie metody o nazwie Rzuty i użycie jej tak jak każdej innej metody Assert. Niestety .NET nie pozwala na napisanie metody rozszerzenia statycznego, więc nie można użyć tej metody, jakby faktycznie należała do kompilacji w klasie Assert; po prostu stwórz inny o nazwie MyAssert lub coś podobnego. Klasa wygląda następująco:
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))
);
}
}
}
}
Oznacza to, że test jednostkowy wygląda następująco:
[TestMethod()]
public void ExceptionTest()
{
String testStr = null;
MyAssert.Throws<NullReferenceException>(() => testStr.ToUpper());
}
Który wygląda i zachowuje się bardziej jak reszta składni testu jednostkowego.
Assert.ThrowsException<T>
i Assert.ThrowsExceptionAsync<T>
- patrz blogs.msdn.microsoft.com/visualstudioalm/2017/02/25/…
jeśli używasz NUNIT, możesz zrobić coś takiego:
Assert.Throws<ExpectedException>(() => methodToTest());
Możliwe jest również zapisanie zgłoszonego wyjątku w celu jego dalszego sprawdzenia:
ExpectedException ex = Assert.Throws<ExpectedException>(() => methodToTest());
Assert.AreEqual( "Expected message text.", ex.Message );
Assert.AreEqual( 5, ex.SomeNumber);
Jeśli używasz MSTest, który pierwotnie nie miał ExpectedException
atrybutu, możesz to zrobić:
try
{
SomeExceptionThrowingMethod()
Assert.Fail("no exception thrown");
}
catch (Exception ex)
{
Assert.IsTrue(ex is SpecificExceptionType);
}
Uważaj na używanie ExpectedException, ponieważ może to prowadzić do kilku pułapek, jak pokazano tutaj:
http://geekswithblogs.net/sdorman/archive/2009/01/17/unit-testing-and-expected-exceptions.aspx
I tu:
http://xunit.github.io/docs/comparisons.html
Jeśli chcesz przetestować wyjątki, sposoby są mniej niezadowolone. Możesz użyć metody try {act / fail} catch {assert}, która może być przydatna w środowiskach, które nie mają bezpośredniego wsparcia dla testów wyjątków innych niż ExpectedException.
Lepszą alternatywą jest użycie xUnit.NET, który jest bardzo nowoczesnym, przyszłościowym i rozszerzalnym środowiskiem testowania jednostek, który wyciągnął wnioski ze wszystkich innych błędów i został ulepszony. Jednym z takich ulepszeń jest Assert.Throws, który zapewnia znacznie lepszą składnię do zapewniania wyjątków.
Możesz znaleźć xUnit.NET na github: http://xunit.github.io/
MSTest (v2) ma teraz funkcję Assert.ThrowsException, której można użyć w następujący sposób:
Assert.ThrowsException<System.FormatException>(() =>
{
Story actual = PersonalSite.Services.Content.ExtractHeader(String.Empty);
});
Możesz zainstalować go za pomocą nuget: Install-Package MSTest.TestFramework
W projekcie, nad którym pracuję, mamy inne rozwiązanie.
Po pierwsze, nie podoba mi się ExpectedExceptionAttribute, ponieważ bierze pod uwagę wywołanie metody, które spowodowało wyjątek.
Zamiast tego robię to za pomocą metody pomocy.
Test
[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;
}
Zgrabnie, prawda?)
Jest to atrybut metody testowej ... nie używasz Assert. Wygląda tak:
[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()
Możesz pobrać pakiet z Nuget, używając: PM> Install-Package MSTestExtensions, który dodaje składnię Assert.Throws () w stylu nUnit / xUnit do MsTest.
Instrukcje wysokiego poziomu: pobierz zestaw i odziedzicz z BaseTest i możesz użyć składni Assert.Throws () .
Główna metoda implementacji rzutów wygląda następująco:
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)));
}
}
Ujawnienie: Złożyłem ten pakiet.
Więcej informacji: http://www.bradoncode.com/blog/2012/01/asserting-exceptions-in-mstest-with.html
Możesz to osiągnąć za pomocą prostej linii.
Jeśli operacja foo.bar()
jest asynchroniczna:
await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());
Jeśli foo.bar()
nie jest asynchroniczne
Assert.ThrowsException<Exception>(() => foo.bar());
ArgumentException
na przykład. Stara Try Catch i testowanie odpowiedzi wyjątku jest nadal preferowana, jeśli masz zaawansowane kryteria testowania, ale w wielu moich przypadkach bardzo to pomaga!
Nie polecam używania atrybutu ExpectedException (ponieważ jest zbyt ograniczający i podatny na błędy) ani pisania bloku try / catch w każdym teście (ponieważ jest zbyt skomplikowany i podatny na błędy). Użyj dobrze zaprojektowanej metody asercji - dostarczonej przez środowisko testowe lub napisz własną. Oto, co napisałem i wykorzystałem.
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));
}
}
Przykładowe zastosowania:
[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)
}
UWAGI
Zwrócenie wyjątku zamiast obsługi wywołania zwrotnego sprawdzania poprawności jest rozsądnym pomysłem, z wyjątkiem tego, że powoduje to, że składnia wywołania tego asertu jest bardzo różna od innych twierdzeń, których używam.
W przeciwieństwie do innych, używam „propaguje” zamiast „rzuca”, ponieważ możemy jedynie przetestować, czy wyjątek propaguje się z wywołania. Nie możemy bezpośrednio przetestować, czy zgłoszony został wyjątek. Ale przypuszczam, że wyobrażenie o rzutach może oznaczać: rzucone i nie złapane.
KOŃCOWA MYŚL
Przed przejściem do tego rodzaju podejścia rozważałem użycie atrybutu ExpectedException, gdy test zweryfikował tylko typ wyjątku i użycie bloku try / catch, jeśli wymagana była większa weryfikacja. Ale nie tylko musiałbym zastanowić się, którą technikę zastosować w każdym teście, ale także zmienić kod z jednej techniki na drugą, gdy zmieniły się potrzeby, nie było trywialnym wysiłkiem. Stosowanie jednego spójnego podejścia oszczędza wysiłek umysłowy.
Podsumowując, podejście to cechuje: łatwość użycia, elastyczność i solidność (trudno to zrobić źle).
Pomocnik udostępniony przez @Richiban powyżej działa świetnie, z tym że nie radzi sobie z sytuacją, w której zgłoszony jest wyjątek, ale nie jest oczekiwanym typem. Następujące adresy, które:
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))
);
}
}
}
}
Ponieważ wspominasz o użyciu innych klas testowych, lepszym rozwiązaniem niż ExpectedException
atrybut jest użycie Shoudly 's Should.Throw .
Should.Throw<DivideByZeroException>(() => { MyDivideMethod(1, 0); });
Powiedzmy, że mamy wymóg, aby klient musiał mieć adres, aby utworzyć zamówienie . Jeśli nie, CreateOrderForCustomer
metoda powinna spowodować ArgumentException
. Następnie moglibyśmy napisać:
[TestMethod]
public void NullUserIdInConstructor()
{
var customer = new Customer(name := "Justin", address := null};
Should.Throw<ArgumentException>(() => {
var order = CreateOrderForCustomer(customer) });
}
Jest to lepsze niż użycie ExpectedException
atrybutu, ponieważ dokładniej określamy, co powinno spowodować błąd. To sprawia, że wymagania w naszych testach są jaśniejsze, a także ułatwia diagnozę, gdy test się nie powiedzie.
Uwaga: istnieje również Should.ThrowAsync
metoda asynchronicznego testowania.
W przypadku wbudowanego testowania jednostkowego VS, jeśli po prostu chcesz sprawdzić, czy zgłoszony został „dowolny wyjątek”, ale nie znasz typu, możesz użyć catch all:
[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void ThrowExceptionTest()
{
//...
}
Cóż, podsumuję to, co wszyscy tu wcześniej mówili ... W każdym razie, oto kod, który zbudowałem zgodnie z dobrymi odpowiedziami :) Pozostaje tylko skopiować i użyć ...
/// <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.");
}
Sprawdź Dokumenty nUnit, aby uzyskać przykłady dotyczące:
[ExpectedException( typeof( ArgumentException ) )]
W przypadku korzystania z NUnit , spróbuj tego:
Assert.That(() =>
{
Your_Method_To_Test();
}, Throws.TypeOf<Your_Specific_Exception>().With.Message.EqualTo("Your_Specific_Message"));
Istnieje niesamowita biblioteka o nazwie NFluent, która przyspiesza i ułatwia sposób pisania swoich twierdzeń .
Łatwo jest napisać twierdzenie o zgłoszeniu wyjątku:
[Test]
public void given_when_then()
{
Check.ThatCode(() => MethodToTest())
.Throws<Exception>()
.WithMessage("Process has been failed");
}
Mimo że jest to stare pytanie, chciałbym dodać do dyskusji nową myśl. Rozszerzyłem wzór Arrange, Act, Assert, którego można się spodziewać, Arrange, Act, Assert. Możesz zrobić oczekiwany wskaźnik wyjątku, a następnie potwierdzić, że został przypisany. Jest to czystsze niż wykonywanie Asertów w bloku catch, pozostawiając sekcję Act głównie tylko dla jednego wiersza kodu, który wywołuje testowaną metodę. Nie musisz także przechodzić do Assert.Fail();
ani return
z wielu punktów w kodzie. Każdy inny zgłoszony wyjątek spowoduje, że test zakończy się niepowodzeniem, ponieważ nie zostanie przechwycony, a jeśli zostanie zgłoszony wyjątek typu oczekiwanego, ale nie był to oczekiwany, potwierdzający w stosunku do wiadomości lub innych właściwości wyjątek pomaga upewnić się, że test nie przejdzie przypadkowo.
[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);
}