Почему вы не можете уловить исключения в Code Contract?
System.Diagnostics.Contracts.ContractException недоступно в моем тестовом проекте. Обратите внимание, что этот код - это чисто я возиться с моей сияющей новой копией Visual Studio, но я хотел бы знать, что я делаю неправильно.
Я использую профессиональное издание VS, поэтому у меня нет статической проверки. Чтобы по-прежнему использовать кодовые контракты (что мне нравится), я решил, что единственный способ, которым мой метод может работать, - поймать исключение, которое вызывается во время выполнения, но я не нахожу это возможным.
TestMethod
[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;
}
Ошибка
Error 1 'System.Diagnostics.Contracts.ContractException' is inaccessible
due to its protection level.
Edit
После некоторых размышлений я пришел к выводу, обсужденному в комментариях, а также к следующему. Учитывая метод, если у этого есть требование, которое может быть выражено в форме кода договора, я бы написал тесты как таковые.
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void value_input_must_be_greater_than_zero()
{
// Arrange
var person = new Person();
// Act
person.Number(-1);
}
Это обеспечит, что контракт является частью кода и не будет удален. Это потребовало бы, чтобы Кодовый контракт фактически выбросил указанное исключение. В некоторых случаях это не требуется.
Ответы
Ответ 1
Это преднамеренно - хотя небольшая боль для тестирования.
Дело в том, что в производственном коде вы никогда не захотите поймать исключение контракта; это указывает на ошибку в вашем коде, поэтому вы не должны ожидать, что это больше, чем произвольные неожиданные исключения, которые вы можете захотеть поймать в верхней части стека вызовов, чтобы вы могли перейти к следующему запросу. В принципе, вы не должны рассматривать исключения контракта как те, которые могут "обрабатываться" как таковые.
Теперь, чтобы проверить, что боль... но вы действительно хотите проверить свои контракты в любом случае? Разве это не похоже на тестирование того, что компилятор останавливает вас от передачи в 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.
}
}
Ответ 2
EDIT: У меня было преобразование и больше не используйте 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);
}
Ответ 3
в vs2010 rtm, полное имя было изменено на "System.Diagnostics.Contracts.__ ContractsRuntime + ContractException". НТН
Ответ 4
Хотя этот вопрос стареет, и ответ уже поставлен, я чувствую, что у меня есть хорошее решение, которое упрощает и читает вещи.
В конце концов, это позволяет нам писать тесты на предварительные условия так же просто, как:
[Test]
public void Test()
{
Assert.That(FailingPrecondition, Violates.Precondition);
}
public void FailingPrecondition() {
Contracts.Require(false);
}
Хорошо, поэтому идея состоит в том, чтобы предоставить переписывателю Code Contracts собственный класс времени выполнения контракта.
Это можно настроить в свойствах сборки в разделе "Способы пользовательской перезаписи" (см. Раздел 7.7 "Руководства пользователя по контрактным контрактам" ):
![1]()
Не забудьте также проверить 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>();
}
Это позволяет нам писать простые, читаемые тесты по нарушениям предварительных условий, как показано выше.