Почему вы не можете поймать исключения Кодового Контракта?
System.Diagnostics.Contracts.ContractException не доступен в моем тестовом проекте. Обратите внимание, что этот код просто сам возился с моей новой блестящей копией Visual Studio, но я хотел бы знать, что я делаю неправильно.
Я использую профессиональную версию VS, поэтому у меня нет статической проверки. Чтобы по-прежнему использовать контракты кода (что мне нравится), я решил, что единственный способ, с помощью которого мой метод может работать, - перехватить исключение, которое выдается во время выполнения, но я не нахожу это возможным.
Метод испытания
[TestMethod, ExpectedException(typeof(System.Diagnostics.Contracts.ContractException))]
public void returning_a_value_less_than_one_throws_exception()
{
var person = new Person();
person.Number();
}
метод
public int Number()
{
Contract.Ensures(Contract.Result<int>() >= 0);
return -1;
}
ошибка
Ошибка 1 "System.Diagnostics.Contracts.ContractException" недоступна из-за его уровня защиты.
редактировать
После некоторых размышлений я пришел к заключению, обсужденному в комментариях, а также к следующему. Учитывая метод, если бы у этого было требование, которое могло быть выражено в форме контракта кода, я бы написал тесты как таковые.
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void value_input_must_be_greater_than_zero()
{
// Arrange
var person = new Person();
// Act
person.Number(-1);
}
Это гарантирует, что контракт является частью кода и не будет удален. Однако это потребует от Кодового контракта фактического выброса указанного исключения. Однако в некоторых случаях это не требуется.
4 ответа
Это намеренно - хотя небольшая боль для тестирования.
Дело в том, что в рабочем коде вы никогда не должны ловить исключение контракта; это указывает на ошибку в вашем коде, поэтому вы не должны ожидать, что произойдет что-то большее, чем произвольные неожиданные исключения, которые вы, возможно, захотите перехватить прямо в верхней части стека вызовов, чтобы перейти к следующему запросу. По сути, вы не должны рассматривать исключения из контракта как те, которые могут быть "обработаны" как таковые.
Теперь для тестирования это боль... но вы действительно хотите проверить свои контракты в любом случае? Разве это не похоже на тестирование того, что компилятор мешает вам передать string
к методу, который имеет int
параметр? Вы объявили контракт, он может быть надлежащим образом задокументирован и применен надлежащим образом (во всяком случае, на основе настроек).
Если вы хотите протестировать исключения из контракта, вы можете поймать Exception
в тесте и проверьте его полное имя, или вы можете возиться с Contract.ContractFailed
событие. Я ожидал бы, что структуры модульного тестирования будут иметь встроенную поддержку для этого со временем - но это займет некоторое время, чтобы добраться туда. В то же время вы, вероятно, хотите иметь служебный метод, чтобы ожидать нарушения контракта. Одна возможная реализация:
const string ContractExceptionName =
"System.Diagnostics.Contracts.__ContractsRuntime.ContractException";
public static void ExpectContractFailure(Action action)
{
try
{
action();
Assert.Fail("Expected contract failure");
}
catch (Exception e)
{
if (e.GetType().FullName != ContractExceptionName)
{
throw;
}
// Correct exception was thrown. Fine.
}
}
РЕДАКТИРОВАТЬ: у меня было преобразование и я больше не использую ExpectedException или этот атрибут ниже, а скорее закодировал некоторые методы расширения:
AssertEx.Throws<T>(Action action);
AssertEx.ThrowsExact<T>(Action action);
AssertEx.ContractFailure(Action action);
Это позволяет мне быть более точным в отношении того, где возникает исключение.
Пример метода ContractFailure:
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Cannot catch ContractException")]
public static void ContractFailure(Action operation)
{
try
{
operation();
}
catch (Exception ex)
{
if (ex.GetType().FullName == "System.Diagnostics.Contracts.__ContractsRuntime+ContractException")
return;
throw;
}
Assert.Fail("Operation did not result in a code contract failure");
}
Я создал атрибут для MSTest, который ведет себя аналогично ExpectedExceptionAttribute:
public sealed class ExpectContractFailureAttribute : ExpectedExceptionBaseAttribute { const string ContractExceptionName = "System.Diagnostics.Contracts.__ContractsRuntime+ContractException"; protected override void Verify(Exception exception) { if (exception.GetType().FullName != ContractExceptionName) { base.RethrowIfAssertException(exception); throw new Exception( string.Format( CultureInfo.InvariantCulture, "Test method {0}.{1} threw exception {2}, but contract exception was expected. Exception message: {3}", base.TestContext.FullyQualifiedTestClassName, base.TestContext.TestName, exception.GetType().FullName, exception.Message ) ); } } }
И это можно использовать аналогично:
[TestMethod, ExpectContractFailure] public void Test_Constructor2_NullArg() { IEnumerable arg = null; MyClass mc = new MyClass(arg); }
В версии vs2010 полное имя было изменено на "System.Diagnostics.Contracts.__ContractsRuntime+ContractException". НТН
Хотя этот вопрос стареет, а ответ уже предоставлен, я чувствую, что у меня есть хорошее решение, которое делает вещи простыми и удобочитаемыми. В конце концов, это позволяет нам писать тесты на предварительных условиях так же просто, как:
[Test]
public void Test()
{
Assert.That(FailingPrecondition, Violates.Precondition);
}
public void FailingPrecondition() {
Contracts.Require(false);
}
Итак, идея состоит в том, чтобы предоставить переписчику Code Contracts специальный класс времени исполнения контракта. Это можно настроить в свойствах сборки в разделе "Пользовательские методы перезаписи" (см. Раздел 7.7 "Руководство по контрактам кода"):
Не забудьте также проверить Call-site Requires Checking
!
Пользовательский класс выглядит примерно так:
public static class TestFailureMethods
{
public static void Requires(bool condition, string userMessage, string conditionText)
{
if (!condition)
{
throw new PreconditionException(userMessage, conditionText);
}
}
public static void Requires<TException>(bool condition, string userMessage, string conditionText) where TException : Exception
{
if (!condition)
{
throw new PreconditionException(userMessage, conditionText, typeof(TException));
}
}
}
Использование пользовательских PreconditionException
класс (в нем нет ничего причудливого!). Кроме того, мы добавляем небольшой вспомогательный класс:
public static class Violates
{
public static ExactTypeConstraint Precondition => Throws.TypeOf<PreconditionException>();
}
Это позволяет нам писать простые читаемые тесты на нарушения предварительных условий, как показано выше.