Лучший способ проверить исключения с помощью Assert, чтобы убедиться, что они будут брошены
вы считаете, что это хороший способ для тестирования? Есть предложения?
Exception exception = null;
try{
//I m sure that an exeption will happen here
}
catch (Exception ex){
exception = ex;
}
Assert.IsNotNull(exception);
Я использую MS Test.
8 ответов:
у меня есть несколько разных моделей, которые я использую. Я использую
ExpectedException
атрибут большую часть времени, когда ожидается исключение. Этого достаточно для большинства случаев, однако, есть некоторые случаи, когда этого недостаточно. Исключение может быть не уловимым-поскольку оно вызывается методом, который вызывается отражением, - или, возможно, я просто хочу проверить, что другие условия удерживаются, скажем, транзакция откатывается или какое-то значение все еще установлено. В этих случаях я заворачиваю его вtry/catch
блок, который ожидает точное исключение, делаетAssert.Fail
если код выполняется успешно, а также ловит общие исключения, чтобы убедиться, что другое исключение не выбрасывается.первый случай:
[TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void MethodTest() { var obj = new ClassRequiringNonNullParameter( null ); }
второй случай:
[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 ) ); } }
Я новичок здесь и не имею репутации, чтобы комментировать или понижать голос, но хотел указать на недостаток в Примере в ответ Энди Уайта:
try { SomethingThatCausesAnException(); Assert.Fail("Should have exceptioned above!"); } catch (Exception ex) { // whatever logging code }
во всех системах модульного тестирования, с которыми я знаком,
Assert.Fail
работает путем создания исключения, поэтому общий улов фактически маскирует сбой теста. ЕслиSomethingThatCausesAnException()
не выбрасывают,Assert.Fail
будет, но это никогда не будет пузыриться на тестовый бегун, чтобы указать на неудачу.Если вам нужно поймайте ожидаемое исключение (т. е. для утверждения определенных деталей, таких как сообщение / свойства исключения), важно поймать конкретный ожидаемый тип, а не базовый класс исключений. Это позволило бы
Assert.Fail
исключение для пузырьков (при условии, что вы не бросаете тот же тип исключения, что и ваша платформа модульного тестирования), но все же разрешите проверку на исключение, которое было брошено вашимSomethingThatCausesAnException()
метод.
теперь, 2017, вы можете сделать это проще с новым MSTest V2 Framework:
Assert.ThrowsException<Exception>(() => myClass.MyMethodWithError());
а в 2.5, Нанит имеет следующий метод-уровень
Assert
S для тестирования исключений:утверждать.Бросает, который будет проверять для точного типа исключения:
Assert.Throws<NullReferenceException>(() => someNullObject.ToString());
и
Assert.Catch
, который будет проверять исключение данного типа или тип исключения, производный от этого типа:Assert.Catch<Exception>(() => someNullObject.ToString());
в стороне, при отладке модульных тестов, которые выбрасывают исключения, вы можете предотвратить VS от разбиваются о исключение.
Edit
просто чтобы привести пример комментария Матфея ниже, возвращение общего
Assert.Throws
иAssert.Catch
- это исключение с типом исключения, которое вы можете изучить для дальнейшей проверки:// The type of ex is that of the generic type parameter (SqlException) var ex = Assert.Throws<SqlException>(() => MethodWhichDeadlocks()); Assert.AreEqual(1205, ex.Number);
к сожалению, MSTest по-прежнему имеет только атрибут ExpectedException (просто показывает, насколько MS заботится о MSTest), который IMO довольно ужасен, потому что он нарушает шаблон Arrange/Act/Assert и не позволяет точно указать, в какой строке кода Вы ожидаете, что произойдет исключение.
когда я использую (/принудительно клиентом) использовать MSTest я всегда использую этот вспомогательный класс:
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."); } }
пример использования:
AssertException.Throws<ArgumentNullException>(() => classUnderTest.GetCustomer(null));
в качестве альтернативы, используя
ExpectedException
атрибут, я иногда определяю два полезных метода для моих тестовых классов:
AssertThrowsException()
принимает делегат и утверждает, что он выдает ожидаемое исключение с ожидаемым сообщением.
AssertDoesNotThrowException()
принимает тот же делегат и утверждает, что он не создает исключение.это сопряжение может быть очень полезно, когда вы хотите проверить, что в одном случае возникает исключение, но не другой.
используя их мой модульный тестовый код может выглядеть так:
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);
красиво и аккуратно, да?
мой
AssertThrowsException()
иAssertDoesNotThrowException()
методы определяются на общем базовом классе следующим образом: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); } }
отметьте тест с помощью атрибута ExpectedExceptionAttribute (это термин в NUnit или MSTest; пользователям других платформ модульного тестирования может потребоваться перевести).
в большинстве платформ модульного тестирования .net вы можете поместить атрибут [ExpectedException] в метод теста. Однако это не может сказать вам, что исключение произошло в месте, где вы ожидали. Вот где xunit.net может помочь.
С xunit у вас есть Assert.Бросает, так что вы можете делать вещи, как это:
[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)); }
[факт] является эквивалентом xunit [TestMethod]