你为什么不能赶上代码合同例外?

System.Diagnostics.Contracts.ContractException不能在我的testing项目中访问。 注意这个代码纯粹是自己搞乱了Visual Studio的新版本,但是我想知道我做错了什么。

我使用VS的专业版,所以我没有静态检查。 为了仍然使用代码合约(我喜欢),我认为我的方法可以工作的唯一方法是捕获在运行时抛出的exception,但我不觉得这是可能的。

testing方法

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

错误

 错误1“System.Diagnostics.Contracts.ContractException”无法访问
由于其保护水平。 

编辑

经过一些更多的思考,我已经得出结论中讨论的结论,以及以下内容。 给定一个方法,如果这个要求可以用代码合同的forms来表示,我会这样写testing。

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

这将确保合同是代码的一部分,并不会被删除。 这将要求代码合同实际上抛出指定的例外。 在某些情况下,这不会被要求。

这是故意的 – 虽然对testing有轻微的痛苦。

关键在于,在生产代码中,你绝对不应该想要捕捉合同exception; 它代表了你的代码中的一个错误,所以你不应该期望任何超出任意意外的exception,你可能想要抓住你的调用堆栈的顶部,所以你可以移动到下一个请求。 基本上你不应该把合同例外视为可以“处理”的合同例外。

现在,testing这是一个痛苦…但是,你真的想要testing你的合同么? 是不是有点像testing,编译器阻止你传递一个string的方法有一个int参数? 您已经声明了合同,可以对其进行适当的logging,并且适当地执行(基于设置,无论如何)。

如果你想testing契约例外,你可以在testing中捕获一个纯粹的Exception ,并检查它的全名,或者你可以用Contract.ContractFailed事件。 我希望随着时间的推移,unit testing框架内置了对此的支持 – 但是要花一点时间才能到达那里。 在此期间,您可能希望有一个实用的方法来期望违反合同。 一个可能的实现:

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

编辑:我有一个转换,不再使用ExpectedException或下面的这个属性,而是编码了一些扩展方法:

 AssertEx.Throws<T>(Action action); AssertEx.ThrowsExact<T>(Action action); AssertEx.ContractFailure(Action action); 

这些让我更加精确的提出exception。

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创build了一个属性,其行为与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); } 

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

虽然这个问题已经越来越老了,而且已经有了答案,我觉得我有一个很好的解决scheme,使事情简单易懂。 最后,它允许我们在前置条件下编写testing,就像下面这样简单:

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

好的,所以我们的想法是为代码合同重写器提供一个自定义合约运行时类。 这可以在程序集的“自定义重写器方法”下的属性中设置(参见代码合同用户手册第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>(); } 

这使我们能够编写简单易读的testing违规前提条件,如上所示。