【问题标题】:How to get PEX to automatically generate inputs for code involving LINQ如何让 PEX 自动为涉及 LINQ 的代码生成输入
【发布时间】:2012-04-01 22:14:21
【问题描述】:

我无法让 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 不断建议您在 // 区域中看到的 Contract.Assert 形式的相同“不变”修复: 我相信这个问题在某种程度上与 Pex 与 Linq 的关系有关,但我不确定

--- 描述 测试失败:ArgumentNullException,值不能为空。 参数名称:来源

[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:值不能为空。 参数名称:source at System.Linq.IQueryable'1 System.Linq.Queryable.Where(System.Linq.IQueryable'1 source, System.Linq.Expressions.Expression'1> predicate) c:\users\moran\documents\visual studio 2010\Projects\PexTuts\PexIQueryable\PexIQueryable\ConsumerOfIhaveIQueryable.cs(29):在 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):在 System.Collections.Generic.IEnumerable'1 PexIQueryable.ConsumerOfIhaveIQueryableTest.GetEntitiesByIds(PexIQueryable.ConsumerOfIhaveIQueryable 目标, System.Int32[] ids)

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

很想在这方面获得一些 cmets...

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

莫兰

【问题讨论】:

  • 我刚刚注意到你发布这个...我有过吗?尽管如此,这是一项有趣的调查!

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


【解决方案1】:

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

至于代码,IMO 代码越多越好,只要它是相关的 - 拥有太多总比太少要好得多,所以这很好。如上所述,请尝试明确代码中的所有假设(例如 Moles 存根(因为有不同的方法可以实现这一点,这是一个必须假设的事情)。

我不是 100% 确定你在问什么。代码失败是因为存根的IWithQueryable object 没有实现QueryableSet() 方法并且该方法返回nullPexAssert 这里不会帮助它弄清楚如何创建一个 LINQ 提供程序,这就是你要求它做的事情。 PexChooseBehavedBehavior.Setup() 只是用默认行为 default(T) 替换对 Moles 存根(没有自定义委托)上的委托的任何调用,这就是为什么 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,你几乎必须编写一个模拟查询提供程序,这并不容易。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-03
    • 2022-01-12
    • 2017-01-26
    • 1970-01-01
    • 2017-10-04
    • 2010-10-04
    相关资源
    最近更新 更多