为什么你不能抓住代码合同例外?

时间:2010-04-14 18:26:18

标签: c# visual-studio-2010 .net-4.0 code-contracts

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

修改

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

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

这将确保合同是代码的一部分,并且不会被删除。这将要求Code Contract实际抛出指定的异常。但在某些情况下,不需要这样做。

4 个答案:

答案 0 :(得分:69)

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

重点是,在生产代码中,您永远不应该想要捕获合同异常;它表示代码中存在错误,因此您不应期望任何可能需要在调用堆栈顶部捕获的任意意外异常,以便您可以转到下一个请求。基本上你不应该将合约例外视为可以“处理”的合约例外。

现在,为了测试这是一个痛苦......但你真的想测试你的合同吗?这有点像测试编译器阻止您将string传递给具有int参数的方法吗?您已经宣布了合同,可以对其进行适当的记录,并进行适当的强制执行(无论如何都要根据设置)。

如果您想要测试合同例外,您可以在测试中捕获一个裸Exception并检查其全名,或者您可以使用{{3}事件。我希望单元测试框架能够随着时间的推移对其进行内置支持 - 但要实现这一目标需要一段时间。与此同时,您可能希望使用实用程序方法来预期合同违规。一种可能的实现方式:

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.
    }
}

答案 1 :(得分:8)

编辑:我进行了转换,不再使用下面的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);
    }

答案 2 :(得分:2)

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

答案 3 :(得分:1)

虽然这个问题已经老了,并且已经提供了答案,但我觉得我有一个很好的解决方案,可以保持简单易读。 最后,它允许我们在前提条件上编写测试,如下所示:

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

这使我们能够对前提条件违规进行简单易读的测试,如上所示。