4

更新帖子:为了避免混淆我在做什么和不做什么,我从根本上编辑了这篇帖子,以包含导致此问题的代码的完整示例。为了使这篇文章无论如何都可读,所有代码都贴在底部。


背景:

我正在编写一个流畅的测试界面(我知道它已经完成,但一半的目的是了解它是如何工作的......),我想myNumber用一个代码行来验证它在 3 到 10 之间

myNumber.ShouldBeLessThan(10).And.ShouldBeGreaterThan(10);
myListOfCars.ShouldNotBeNull().And.ShouldBeA<IEnumerable<Car>>();

我想你可以通过阅读第二行看到它应该验证的内容。当然还有更复杂的测试用例...

为了启用.And语法,我引入了一个名为 的帮助器类型AndHelper,它由每个验证方法返回,并且具有一个And返回任何测试内容的属性。所以.And在前面的例子中,应该返回myNumber,所以我也可以测试另一个条件。

我正在使用代码合同,除此之外,我正在验证其中this一些扩展的参数是非空的。这就是导致我的问题的原因。


我的问题:

在我的代码上运行代码合同检查时,我收到一堆警告,例如ShouldBeA<T>无法验证非空要求。我试图通过AndHelper<T>使用两个类ReferenceAndHelper<T>和, 子类来解决这个问题StructAndHelper<T>,并且ReferenceAndHelper<T>具有应保证满足非空要求的合同。但是,这似乎不起作用。

每次我使用其中一个测试扩展时,都会收到两条警告消息。一个声明合同“实例!= null”无法验证,另一个声明位置。第一个指向我使用该方法的行(例如我的第一个示例中的第 2 行),第二个指向指定合同的行,// (1)在我的代码中标记为 。


我的代码:

请耐心等待,这篇文章的这一部分很长。我不知道发布大量代码的 SO 准则是什么(这仍然是相关的),但如果有更好的方法,请赐教。

请注意,本节中的代码不会导致此特定错误,但会限制解决方案。例如,我必须有一个AndHelper<T>不知道类/结构的类型(或子类)。

几个测试:

// This test requires that instance != null, and therefore works
// with ReferenceAndHelper<T>
public static ReferenceAndHelper<T> ShouldBeA<T>(this object instance, string message = "")
    where T : class
{
    Contract.Requires<ArgumentNullException>(instance != null); // (1)
    Contract.Ensures(Contract.Result<ReferenceAndHelper<T>>() != null);

    Assert.IsInstanceOf<T>(instance, message.AsNullIfWhitespace() ?? string.Format("ShouldBeA<{0}> failed.", typeof(T).Name));
    return new ReferenceAndHelper<T>((T)instance);
}

// This test should work for both class and struct types T, and therefore
// cannot decide between StructAndHelper<T> and ReferenceAndHelper<T>.
// The base class is used.
public static AndHelper<T> ShouldBeGreaterThan<T>(this T actual, T expected, string message = "")
    where T : IComparable
{
    Contract.Ensures(Contract.Result<AndHelper<T>>() != null);

    (actual.CompareTo(expected) > 0).ShouldBeTrue(message.AsNullIfEmpty() ?? string.Format("ShouldBeGreaterThan failed. {0} is not greater than {1}", actual.ToString(), expected.ToString()));
    return new AndHelper<T>(actual);
}

// This is the test that returns the AndHelper<T> that .And is called on.
// It is, as you can see, in all possible ways specified that this will be a
// ReferenceAndHelper<T>, which has contracts to ensure that the value is not null.
public static ReferenceAndHelper<T> ShouldNotBeNull<T>(this T value, string message = "")
    where T : class
{
    Contract.Requires<ArgumentNullException>(value != null);
    Contract.Ensures(Contract.Result<ReferenceAndHelper<T>>() != null);

    Assert.IsNotNull(value, message.AsNullIfWhitespace() ?? "ShouldNotBeNull failed.");
    return new ReferenceAndHelper<T>(value);
}

AndHelper<T>班级:

public class AndHelper<T>
{
    protected readonly T val;

    public AndHelper(T value)
    {
        this.val = value;
    }

    public virtual T And { get { return this.val; } }
}

两个子类ReferenceAndHelper<T>

public class ReferenceAndHelper<T> : AndHelper<T>
    where T : class
{
    public ReferenceAndHelper(T value)
        : base(value)
    {
        Contract.Requires(value != null);
    }

    public override T And
    {
        get
        {
            Contract.Ensures(Contract.Result<T>() != null);
            return val;
        }
    }

    [ContractInvariantMethod]
    void ValueIsNotNullInvariant()
    {
        Contract.Invariant(this.val != null);
    }
}

StructAndHelper<T>

public class StructAndHelper<T> : AndHelper<T>
    where T : struct
{
    public StructAndHelper(T value)
        : base(value)
    {
    }

    public override T And
    {
        get
        {
            return this.val;
        }
    }
}
4

3 回答 3

1

AndHelper<T>您是否可以简单地创建一个NonNullAndHelper<T>断言其值不为空的不变量,而不是创建具有不同约束的两个类?这只会由可以保证其结果非空的辅助函数返回,无论是由于需求还是作为其函数的副作用(如 IsNotNull)。这应该允许合同证明。

于 2011-03-08T19:00:19.293 回答
1

代码合同无法验证 And(AndHelper 上的属性)永远不会返回 null

为什么不?除非我误解了您的问题,否则您可以编写如下代码:

public class AndHelper<T>
{
    protected readonly T val;
    public T And { get { return val; } }

    public AndHelper(T value)
    {
        Contract.Requires(value != null);
        val = value; 
    }

    [ContractInvariantMethod]
    void Invariants()
    {
        Contract.Invariant(And != null);
    }
}

从那里开始,合同检查器将确保 And 值永远不会为空。

我误解了你的问题吗?

于 2011-03-08T19:21:50.870 回答
0

I know this is an old question, but I see no accepted answers, so thought I'd take a shot.

Rather than having two subclasses of AndHelper<T>, change your AndHelper<T> to be the following:

public class AndHelper<T>
{
    private readonly T val;

    public AndHelper(T value)
    {
        Contract.Requires(!ReferenceEquals(value, null));

        this.val = value;
    }

    public virtual T And 
    { 
        get 
        { 
            Contract.Ensures(!ReferenceEquals(Contract.Result<T>(), null));

            return this.val; 
        } 
    }

    [ContractInvariantMethod]
    private void ObjectInvariant()
    {
        Contract.Invariant(!ReferenceEquals(val, null));
    }

}

ReferenceEquals(object, object) does not raise the warning for generic types but guarantees they are not null.

于 2013-02-17T23:47:47.787 回答