How come you cannot catch Code Contract exceptions?

给你一囗甜甜゛ 提交于 2019-11-28 04:05:18

This is deliberate - although a slight pain for testing.

The point is that in production code you should never want to catch a contract exception; it indicates a bug in your code, so you shouldn't be expecting that any more than arbitrary unexpected exceptions which you may want to catch right at the top of your call stack so you can move onto the next request. Basically you shouldn't view contract exceptions as ones which can be "handled" as such.

Now, for testing that's a pain... but do you really want to test your contracts anyway? Isn't that a bit like testing that the compiler stops you from passing in a string to a method which has an int parameter? You've declared the contract, it can be documented appropriately, and enforced appropriately (based on settings, anyway).

If you do want to test contract exceptions, you can either catch a bare Exception in the test and check its full name, or you can mess around with the Contract.ContractFailed event. I would expect unit testing frameworks to have built-in support for this over time - but it'll take a little while to get there. In the meantime you probably want to have a utility method to expect a contract violation. One possible implementation:

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

EDIT: I have had a conversion and no longer use either ExpectedException or this attribute below, but rather have coded some extension methods:

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

These allow me to be more precise about where the exception is raised.

Example of ContractFailure method:

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

I created an attribute for MSTest, that behaves similarly to the 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
                )
            );
        }
    }
}

And this can be used similarly:

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

        MyClass mc = new MyClass(arg);
    }

in the vs2010 rtm, the full name has been changed to "System.Diagnostics.Contracts.__ContractsRuntime+ContractException". HTH

Though this question is getting old, and an answer has already been supplied, I feel like I have a nice solution that keeps things simple and readable. In the end, it allows us to write tests on preconditions as simple as:

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

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

Okay, so the idea is to provide the Code Contracts rewriter with a custom contract runtime class. This can be setup in the assembly's properties under "Custom Rewriter Methods" (see the Code Contracts User Manual section 7.7):

Remember to also check Call-site Requires Checking!

The custom class looks something like this:

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

Using a custom PreconditionException class (it contains nothing fancy!). We furthermore add a small helper class:

public static class Violates
{
    public static ExactTypeConstraint Precondition => Throws.TypeOf<PreconditionException>();
}

This allows us to write simple, readable tests on precondition violations, as shown above.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!