如何让PEX自动为涉及LINQ的代码生成输入

时间:2012-04-01 22:14:22

标签: linq unit-testing mstest code-contracts pex

我无法让PEX自动覆盖调用Linq扩展方法的方法,例如本例中的Where()和Contains():

public class MyEntity
{
    public int Id { get; set; }
}

public interface IWithQueryable
{
    IQueryable<MyEntity> QueryableSet();
}

public class ConsumerOfIhaveIQueryable
{
    private readonly IWithQueryable _withIQueryable;
    public ConsumerOfIhaveIQueryable(IWithQueryable withIQueryable)
    {
        // <pex>
        Contract.Requires<ArgumentNullException>(
            withIQueryable != null, "withIQueryable");
        // </pex>
        _withIQueryable = withIQueryable;
    }

    public IEnumerable<MyEntity> GetEntitiesByIds(IEnumerable<int> ids)
    {
        Contract.Requires<ArgumentNullException>(ids != null, "ids");
        // <pex>
        Contract.Assert
            (this._withIQueryable.QueryableSet() != (IQueryable<MyEntity>)null);
        // </pex>
        IEnumerable<MyEntity> entities =
        _withIQueryable.QueryableSet().Where(
            entity => ids.Contains(entity.Id));
        if (entities.Count() != ids.Count())
        {
            return null;
        }
        return entities;
    }
}

[PexClass(typeof(ConsumerOfIhaveIQueryable))]
[PexAllowedExceptionFromTypeUnderTest(typeof(InvalidOperationException))]
[PexAllowedExceptionFromTypeUnderTest(typeof(ArgumentException), AcceptExceptionSubtypes = true)]
[TestClass]
public partial class ConsumerOfIhaveIQueryableTest
{
    [PexMethod]
    public IEnumerable<MyEntity> GetEntitiesByIds(
        [PexAssumeUnderTest]ConsumerOfIhaveIQueryable target,
        int[] ids)
    {
        var result = target.GetEntitiesByIds(ids);
        PexAssert.IsTrue(result.Count() == ids.Length);
        return result;
    }
}

当我在这个PexMethod上运行PEX探索时,我发现了以下问题:

  • 我一直得到同样的异常,并且PEX一直以契约的形式建议相同的“不变”修复。您在//区域中看到的断言: 我认为这个问题与Pex与Linq的关系有什么关系,但我不确定

---描述 测试失败:ArgumentNullException,Value不能为null。 参数名称:source

[TestMethod]
[PexGeneratedBy(typeof(ConsumerOfIhaveIQueryableTest))]
[PexRaisedException(typeof(ArgumentNullException))]
public void GetEntitiesByIdsThrowsArgumentNullException385()
{
    using (PexChooseBehavedBehavior.Setup())
    {
      SIWithQueryable sIWithQueryable;
      ConsumerOfIhaveIQueryable consumerOfIhaveIQueryable;
      IEnumerable<MyEntity> iEnumerable;
      sIWithQueryable = new SIWithQueryable();
      consumerOfIhaveIQueryable =
        ConsumerOfIhaveIQueryableFactory.Create((IWithQueryable)sIWithQueryable);
      int[] ints = new int[0];
      iEnumerable = this.GetEntitiesByIds(consumerOfIhaveIQueryable, ints);
    }
}

---例外细节

System.ArgumentNullException:值不能为null。 参数名称:System.Linq.IQueryable'1 System.Linq.Queryable.Where(System.Linq.IQueryable'1 source,System.Linq.Expressions.Expression'1&gt;谓词)中的source     c:\ users \ moran \ documents \ visual studio 2010 \ Projects \ PexTuts \ PexIQueryable \ PexIQueryable \ ConsumerOfIhaveIQueryable.cs(29):at System.Collections.Generic.IEnumerable'1 PexIQueryable.ConsumerOfIhaveIQueryable.GetEntitiesByIds(System.Collections.Generic。 IEnumerable`1 ids)     c:\ users \ moran \ documents \ visual studio 2010 \ Projects \ PexTuts \ PexIQueryable \ PexIQueryable.Tests \ ConsumerOfIhaveIQueryableTest.cs(34):at System.Collections.Generic.IEnumerable'1 PexIQueryable.ConsumerOfIhaveIQueryableTest.GetEntitiesByIds(PexIQueryable.ConsumerOfIhaveIQueryable target) ,System.Int32 [] ids)

  • 我无法让PEX生成相关输入。正如你所看到的,我试图通过在我的代码中添加一个PexAssert和一个分支来“帮助”它,但是这个分支从未被覆盖,即使它应该相对简单地生成一个代码,走那条路。 PEX只尝试传递null或空数组作为Ids列表(我在某处读到PEX更容易使用数组(int [])而不是IEnumerable。

很想对此发表评论......

顺便说一句,这是我的第一篇SO帖子,希望我没有太多代码和信息的垃圾邮件。

Moran的

1 个答案:

答案 0 :(得分:1)

在为此设置代码一段时间后,我做了一些假设。我假设您通过Moles存根对IWithQueryable进行了存根,并且当您删除了QueryableSet()方法不返回null的Contract断言时也会发生NullArgumentException。

对于代码来说,IMO代码越多越好,只要它相关 - 就好了太多而不能继续下去,所以没关系。如上所述,请尽量明确代码中的所有假设(例如Moles stubbing(因为有不同的方法来实现这一点,这是人们必须承担的)。

我不是100%肯定你在问什么。代码失败,因为存根IWithQueryable object没有QueryableSet()方法的实现,并且该方法返回null。这里的PexAssert无法帮助它弄清楚如何创建一个LINQ提供程序,这就是你要求它做的事情。 PexChooseBehavedBehavior.Setup()只是替换对Moles存根(没有自定义委托)的委托的任何调用,其默认行为为default(T),这就是source为空的原因 - QueryableSet()初始化为null

您可以通过几种方式解决此问题(至少在提供创建QueryableSet()方法的方法的意义上)。您可以创建工厂方法来生成整个SIWithQueryable或仅QueryableSet委托。这是Pex建议的东西(然而,在我看来它的类型和名称空间混乱了)。例如:

/// <summary>A factory for Microsoft.Moles.Framework.MolesDelegates+Func`1[System.Linq.IQueryable`1[StackOverflow.Q9968801.MyEntity]] instances</summary>
public static partial class MolesDelegatesFactory
{
    /// <summary>A factory for Microsoft.Moles.Framework.MolesDelegates+Func`1[System.Linq.IQueryable`1[StackOverflow.Q9968801.MyEntity]] instances</summary>
    [PexFactoryMethod(typeof(MolesDelegates.Func<IQueryable<MyEntity>>))]
    public static MolesDelegates.Func<IQueryable<MyEntity>> CreateFunc()
    {
        throw new InvalidOperationException();

        // TODO: Edit factory method of Func`1<IQueryable`1<MyEntity>>
        // This method should be able to configure the object in all possible ways.
        // Add as many parameters as needed,
        // and assign their values to each field by using the API.
    }

    /// <summary>A factory for Microsoft.Moles.Framework.MolesDelegates+Func`1[System.Linq.IQueryable`1[StackOverflow.Q9968801.MyEntity]] instances</summary>
    [PexFactoryMethod(typeof(SIWithQueryable))]
    public static SIWithQueryable Create()
    {
        var siWithQueryable = new SIWithQueryable();
        siWithQueryable.QueryableSet = () => { throw new InvalidOperationException(); };

        return siWithQueryable;
        // TODO: Edit factory method of Func`1<IQueryable`1<MyEntity>>
        // This method should be able to configure the object in all possible ways.
        // Add as many parameters as needed,
        // and assign their values to each field by using the API.
    }
}

然后将其连接到测试方法,其中两行之一分配sIWithQueryable

[TestMethod]
[PexGeneratedBy(typeof(ConsumerOfIhaveIQueryableTest))]
public void GetEntitiesByIdsThrowsArgumentNullException678()
{
  SIWithQueryable sIWithQueryable;

  // Either this for the whole object.
  sIWithQueryable = MolesDelegatesFactory.Create();

  // Or this for just that delegate.
  sIWithQueryable = new SIWithQueryable();
  sIWithQueryable.QueryableSet = MolesDelegatesFactory.CreateFunc();

  ConsumerOfIhaveIQueryable consumerOfIhaveIQueryable;
  IEnumerable<MyEntity> iEnumerable;
  consumerOfIhaveIQueryable = ConsumerOfIhaveIQueryableFactory.Create((IWithQueryable) sIWithQueryable);
  int[] ints = new int[0];
  iEnumerable = this.GetEntitiesByIds(consumerOfIhaveIQueryable, ints);
}

这将在为IWithQueryable创建存根时调用工厂方法。这仍然是一个问题,因为重新启动探测将消除存根设置。

如果您提供无参数工厂方法来创建存根(MolesDelegatesFactory.CreateFunc()),那么Pex将了解这一点并生成测试以使用它。因此,它将正确管理测试重新生成的行为。不幸的是,Pex建议将此委托创建为工厂方法 - 但是,它永远不会被调用,默认实现总是被使用,似乎必须模拟父类型。

但是,我想知道你为什么要创建一个简单包装另一个的接口IWithQueryable,以及你期望用IQueryable做什么。为了做一些非常有用的事情,你需要做很多工作来处理IQueryable界面 - 主要是ProviderExpression,你会非常喜欢写一个模拟查询提供程序,这并不容易。