Как заставить PEX автоматически генерировать входы для кода, включающего LINQ - PullRequest
2 голосов
/ 02 апреля 2012

У меня проблемы с получением 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;
    }
}

Когда я запускаю исследование PEX на этом PexMethod, я вижу следующие проблемы:

  • Я продолжаю получать одно и то же исключение, и PEX продолжает предлагать одно и то же "инвариантное" исправление в форме Контракта. Утверждаем, что вы видите в // регионе: Я считаю, что проблема как-то связана с тем, как Пекс относится к Линку, но я не уверен

--- Описание провальный тест: 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: значение не может быть нулевым. Имя параметра: источник в System.Linq.IQueryable'1 System.Linq.Queryable.Where (источник System.Linq.IQueryable'1, System.Linq.Expressions.Expression'1> предикат) c: \ users \ moran \ documents \ visual studio 2010 \ Projects \ PexTuts \ PexIQueryable \ PexIQueryable \ ConsumerOfIhaveIQueryable.cs (29): в System.Collections.Generic.IEnumerable'1 PexIQueryable.ConsumerOfIhaveIGeIgnEGenSignSignSignSignE. IEnumerable`1 идентификаторов) c: \ users \ moran \ documents \ visual studio 2010 \ Projects \ PexTuts \ PexIQueryable \ PexIQueryable.Tests \ ConsumerOfIhaveIQueryableTest.cs (34): в объекте System.Collections.Generic.IEnumerable'1 PexIQueryIueIUIIBEIBLEUIBEUGUEUIBEUIES , System.Int32 [] идентификаторы)

  • Я не могу заставить PEX генерировать соответствующие входные данные. Как вы можете видеть, я попытался «помочь», добавив PexAssert и ветвь в моем коде, но эта ветвь никогда не покрывалась, хотя она должна быть относительно простой для генерации кода, который будет работать этот путь. PEX только пытается передать пустой или пустой массив в качестве списка идентификаторов (я где-то читал, что PEX легче работать с массивами (int []), чем с IEnumerable.

Хотелось бы получить некоторые комментарии к этому ...

Кстати, это мой первый пост, надеюсь, я не спамил слишком много кода и информации.

Moran

1 Ответ

1 голос
/ 11 апреля 2012

Через некоторое время, настроив код для этого, я сделал несколько предположений.Я предполагаю, что вы заглушаете IWithQueryable через заглушку Moles, а также, что NullArgumentException возникает, когда вы удаляете утверждение Contract, что метод QueryableSet () не возвращает null.

Что касается кода,ИМО: чем больше кода, тем лучше, если это актуально - гораздо лучше иметь слишком много, чем слишком мало, чтобы продолжать, так что это нормально.Как и выше, постарайтесь прояснить все предположения в коде (например, заглушки Moles (так как есть разные способы для достижения этого, и это то, что нужно предположить).

Я не уверен на 100%, чтовы спрашиваете. Код терпит неудачу, потому что у заглушки IWithQueryable object нет имплементации для метода QueryableSet(), и этот метод возвращает null. PexAssert здесь не поможет понять, как создатьпровайдер LINQ, что вы и просите. PexChooseBehavedBehavior.Setup() просто заменяет любые вызовы делегатов на заглушках Moles (у которых нет собственного делегата) поведением по умолчанию, равным default(T), поэтомувот почему source равен нулю - QueryableSet() инициализируется null.

Вы можете решить эту проблему несколькими способами (по крайней мере, в смысле обеспечения способа создания QueryableSet()method). Вы можете создать фабричный метод для генерации либо всего SIWithQueryable, либо просто делегата QueryableSet. Это то, что предлагает Пекс (однако, со мной он запутал типы и пространства имен).r экземпляр:

/// <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 предлагает создать этот делегат как фабричный метод - однако, он никогда не вызывается, реализация по умолчанию всегда используется, кажется, что нужно высмеивать родительский тип.

Однако мне интересно, почему высоздаем интерфейс IWithQueryable, который просто оборачивает другой, а также то, что вы ожидаете сделать с IQueryable.Чтобы сделать что-то очень полезное, вам придется проделать большую работу, связанную с интерфейсом IQueryable - в основном Provider и Expression, вам, скорее всего, придется написать провайдера ложных запросов., что будет нелегко.

...