c#-为什么您无法捕获代码合同异常?

我的测试项目中无法访问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;
}

错误

Error 1 'System.Diagnostics.Contracts.ContractException' is inaccessible
due to its protection level.

编辑

经过更多的思考之后,我得出了评论中以及以下内容中讨论的结论。 给定一个方法,如果有一个可以以代码合同形式表达的要求,我将这样编写测试。

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void value_input_must_be_greater_than_zero()
{
    // Arrange
    var person = new Person();
    // Act
    person.Number(-1);
}

这样可以确保合同是代码的一部分,并且不会被删除。 但是,这将要求代码合同实际引发指定的异常。 在某些情况下,这不是必需的。

4个解决方案
69 votes

这是故意的-尽管测试有些痛苦。

关键是,在生产代码中,您永远都不应希望捕获合同异常。 它表示代码中的错误,因此您不应该期望在调用堆栈的顶部可能捕获的任意意外异常之外的任何其他异常,因此可以继续下一个请求。 基本上,您不应该将合同例外视为可以“处理”的例外。

现在,进行测试很痛苦……但是您是否真的想测试合同? 有点像测试编译器阻止您将Contract.ContractFailed传递给具有int参数的方法吗? 您已经声明了合同,可以对其进行适当地记录和执行(无论如何基于设置)。

如果您确实想测试合同例外,则可以在测试中捕获裸露的Contract.ContractFailed,并检查其全名,也可以随意处理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.
    }
}
Jon Skeet answered 2020-06-20T07:31:13Z
8 votes

编辑:我进行了转换,不再使用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:

    [TestMethod, ExpectContractFailure]
    public void Test_Constructor2_NullArg()
    {
        IEnumerable arg = null;

        MyClass mc = new MyClass(arg);
    }

这可以类似地使用:

    [TestMethod, ExpectContractFailure]
    public void Test_Constructor2_NullArg()
    {
        IEnumerable arg = null;

        MyClass mc = new MyClass(arg);
    }
Stephen Drew answered 2020-06-20T07:31:59Z
2 votes

在vs2010 rtm中,全名已更改为“ System.Diagnostics.Contracts .__ ContractsRuntime + ContractException”。 高温超导

user341451 answered 2020-06-20T07:32:19Z
1 votes

尽管这个问题越来越老了,并且已经提供了答案,但我觉得我有一个很好的解决方案,可以使事情简单易懂。最后,它使我们可以根据以下简单条件编写测试:

[Test]
public void Test()
{
    Assert.That(FailingPrecondition, Violates.Precondition);
}

public void FailingPrecondition() {
    Contracts.Require(false);
}

好的,所以我们的想法是为代码合同重写器提供一个自定义合同运行时类。可以在“自定义重写器方法”下的程序集属性中进行设置(请参见《代码合同用户手册》第7.7节):

1

请记住还要检查PreconditionException

自定义类如下所示:

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>();
}

如上所述,这使我们能够针对前提条件违规编写简单易读的测试。

Mikkel R. Lund answered 2020-06-20T07:33:02Z
translate from https://stackoverflow.com:/questions/2639960/how-come-you-cannot-catch-code-contract-exceptions