代码之家  ›  专栏  ›  技术社区  ›  Finglas

为什么不能捕获代码契约异常?

  •  46
  • Finglas  · 技术社区  · 16 年前

    我使用的是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 回复  |  直到 11 年前
        1
  •  70
  •   Jon Skeet    16 年前

    这是有意为之的——尽管测试有点痛苦。

    关键是,在生产代码中,您永远不应该希望捕获契约异常;它表示代码中有一个bug,因此您不应该期望在调用堆栈的顶部捕捉到更多的意外异常,这样您就可以进入下一个请求。基本上你不应该把合同例外看作是可以“处理”的例外。

    现在,对于测试来说那是一种痛苦。。。但是你真的想测试你的合同吗?这不是有点像测试编译器阻止你传递 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
  •  8
  •   Stephen Drew    12 年前

    编辑:

    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创建了一个属性,其行为类似于 预期异常属性:

    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
  •  2
  •   user341451    16 年前

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

        4
  •  1
  •   Mikkel R. Lund    10 年前

    虽然这个问题已经过时了,而且已经给出了答案,但我觉得我有一个很好的解决方案,可以让事情简单易懂。 最后,它允许我们在前提条件下编写测试,简单如下:

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

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

    这使我们能够编写简单的、可读的关于前提条件冲突的测试,如上所示。