Подтвердить что ты не робот

Почему вы не можете уловить исключения в 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);
}

Это обеспечит, что контракт является частью кода и не будет удален. Это потребовало бы, чтобы Кодовый контракт фактически выбросил указанное исключение. В некоторых случаях это не требуется.

4b9b3361

Ответ 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>();
}

Это позволяет нам писать простые, читаемые тесты по нарушениям предварительных условий, как показано выше.