Как сказать Pex не заглушать абстрактный класс, который имеет конкретные реализации

Я пытаюсь использовать Pex для проверки кода. У меня есть абстрактный класс с четырьмя конкретными реализациями. Я создал фабричные методы для каждого из четырех конкретных типов. Я также создал один для абстрактного типа, за исключением того, что этот хороший поток объясняет, что Пекс не будет использовать метод абстрактной фабрики, равно как и не должен.

Проблема заключается в том, что часть моего кода зависит от того, какие четыре конкретных типа существуют (поскольку очень и очень маловероятно, что будут созданы какие-либо дополнительные подклассы), но Pex нарушает код, используя Moles для создания заглушки.

Как я могу заставить Pex использовать один из фабричных методов (любой, мне все равно) для создания экземпляров абстрактного класса, даже не создавая заглушки Moles для этого абстрактного класса? Есть ли PexAssume Директива, которая будет выполнять это? Обратите внимание, что некоторые из конкретных типов образуют тип древовидной структуры, так сказать ConcreteImplementation происходит от AbstractClass, а также ConcreteImplementation имеет два свойства типа AbstractClass, Мне нужно убедиться, что никакие заглушки не используются нигде в дереве. (Не все конкретные реализации имеют AbstractClass Свойства).

Редактировать:

Похоже, мне нужно добавить еще немного информации о том, как работает сама структура классов, хотя помните, что цель по-прежнему состоит в том, чтобы заставить Pex не заглушать классы.

Здесь представлены упрощенные версии абстрактного базового класса и четыре его конкретные реализации.

public abstract class AbstractClass
{
    public abstract AbstractClass Distill();

    public static bool operator ==(AbstractClass left, AbstractClass right)
    {
         // some logic that returns a bool
    }

    public static bool operator !=(AbstractClass left, AbstractClass right)
    {
         // some logic that basically returns !(operator ==)
    }

    public static Implementation1 Implementation1
    {
        get
        {
            return Implementation1.GetInstance;
        }
    }
}

public class Implementation1 : AbstractClass, IEquatable<Implementation1>
{
    private static Implementation1 _implementation1 = new Implementation1();

    private Implementation1()
    {
    }

    public override AbstractClass Distill()
    {
        return this;
    }

    internal static Implementation1 GetInstance
    {
        get
        {
            return _implementation1;
        }
    }

    public bool Equals(Implementation1 other)
    {
        return true;
    }
}

public class Implementation2 : AbstractClass, IEquatable<Implementation2>
{
    public string Name { get; private set; }
    public string NamePlural { get; private set; }

    public Implementation2(string name)
    {
        // initializes, including
        Name = name;
        // and sets NamePlural to a default
    }

    public Implementation2(string name, string plural)
    {
        // initializes, including
        Name = name;
        NamePlural = plural;
    }

    public override AbstractClass Distill()
    {
        if (String.IsNullOrEmpty(Name))
        {
            return AbstractClass.Implementation1;
        }
        return this;
    }

    public bool Equals(Implementation2 other)
    {
        if (other == null)
        {
            return false;
        }

        return other.Name == this.Name;
    }
}

public class Implementation3 : AbstractClass, IEquatable<Implementation3>
{
    public IEnumerable<AbstractClass> Instances { get; private set; }

    public Implementation3()
        : base()
    {
        Instances = new List<AbstractClass>();
    }

    public Implementation3(IEnumerable<AbstractClass> instances)
        : base()
    {
        if (instances == null)
        {
            throw new ArgumentNullException("instances", "error msg");
        }

        if (instances.Any<AbstractClass>(c => c == null))
        {
            thrown new ArgumentNullException("instances", "some other error msg");
        }

        Instances = instances;
    }

    public override AbstractClass Distill()
    {
        IEnumerable<AbstractClass> newInstances = new List<AbstractClass>(Instances);

        // "Flatten" the collection by removing nested Implementation3 instances
        while (newInstances.OfType<Implementation3>().Any<Implementation3>())
        {
            newInstances = newInstances.Where<AbstractClass>(c => c.GetType() != typeof(Implementation3))
                                       .Concat<AbstractClass>(newInstances.OfType<Implementation3>().SelectMany<Implementation3, AbstractUnit>(i => i.Instances));
        }

        if (newInstances.OfType<Implementation4>().Any<Implementation4>())
        {
            List<AbstractClass> denominator = new List<AbstractClass>();

            while (newInstances.OfType<Implementation4>().Any<Implementation4>())
            {
                denominator.AddRange(newInstances.OfType<Implementation4>().Select<Implementation4, AbstractClass>(c => c.Denominator));
                newInstances = newInstances.Where<AbstractClass>(c => c.GetType() != typeof(Implementation4))
                                           .Concat<AbstractClass>(newInstances.OfType<Implementation4>().Select<Implementation4, AbstractClass>(c => c.Numerator));
            }

            return (new Implementation4(new Implementation3(newInstances), new Implementation3(denominator))).Distill();
        }

        // There should only be Implementation1 and/or Implementation2 instances
        // left.  Return only the Implementation2 instances, if there are any.
        IEnumerable<Implementation2> i2s = newInstances.Select<AbstractClass, AbstractClass>(c => c.Distill()).OfType<Implementation2>();
        switch (i2s.Count<Implementation2>())
        {
            case 0:
                return AbstractClass.Implementation1;
            case 1:
                return i2s.First<Implementation2>();
            default:
                return new Implementation3(i2s.OrderBy<Implementation2, string>(c => c.Name).Select<Implementation2, AbstractClass>(c => c));
        }
    }

    public bool Equals(Implementation3 other)
    {
        // omitted for brevity
        return false;
    }
}

public class Implementation4 : AbstractClass, IEquatable<Implementation4>
{
    private AbstractClass _numerator;
    private AbstractClass _denominator;

    public AbstractClass Numerator
    {
        get
        {
            return _numerator;
        }

        set
        {
            if (value == null)
            {
                throw new ArgumentNullException("value", "error msg");
            }

            _numerator = value;
        }
    }

    public AbstractClass Denominator
    {
        get
        {
            return _denominator;
        }

        set
        {
            if (value == null)
            {
                throw new ArgumentNullException("value", "error msg");
            }
            _denominator = value;
        }
    }

    public Implementation4(AbstractClass numerator, AbstractClass denominator)
        : base()
    {
        if (numerator == null || denominator == null)
        {
            throw new ArgumentNullException("whichever", "error msg");
        }

        Numerator = numerator;
        Denominator = denominator;
    }

    public override AbstractClass Distill()
    {
        AbstractClass numDistilled = Numerator.Distill();
        AbstractClass denDistilled = Denominator.Distill();

        if (denDistilled.GetType() == typeof(Implementation1))
        {
            return numDistilled;
        }
        if (denDistilled.GetType() == typeof(Implementation4))
        {
            Implementation3 newInstance = new Implementation3(new List<AbstractClass>(2) { numDistilled, new Implementation4(((Implementation4)denDistilled).Denominator, ((Implementation4)denDistilled).Numerator) });
            return newInstance.Distill();
        }
        if (numDistilled.GetType() == typeof(Implementation4))
        {
            Implementation4 newImp4 = new Implementation4(((Implementation4)numReduced).Numerator, new Implementation3(new List<AbstractClass>(2) { ((Implementation4)numDistilled).Denominator, denDistilled }));
            return newImp4.Distill();
        }

        if (numDistilled.GetType() == typeof(Implementation1))
        {
            return new Implementation4(numDistilled, denDistilled);
        }

        if (numDistilled.GetType() == typeof(Implementation2) && denDistilled.GetType() == typeof(Implementation2))
        {
            if (((Implementation2)numDistilled).Name == (((Implementation2)denDistilled).Name)
            {
                return AbstractClass.Implementation1;
            }
            return new Implementation4(numDistilled, denDistilled);
        }

        // At this point, one or both of numerator and denominator are Implementation3
        // instances, and the other (if any) is Implementation2.  Because both
        // numerator and denominator are distilled, all the instances within either
        // Implementation3 are going to be Implementation2.  So, the following should
        // work.
        List<Implementation2> numList =
            numDistilled.GetType() == typeof(Implementation2) ? new List<Implementation2>(1) { ((Implementation2)numDistilled) } : new List<Implementation2>(((Implementation3)numDistilled).Instances.OfType<Implementation2>());

        List<Implementation2> denList =
            denDistilled.GetType() == typeof(Implementation2) ? new List<Implementation2>(1) { ((Implementation2)denDistilled) } : new List<Implementation2>(((Implementation3)denDistilled).Instances.OfType<Implementation2>());

        Stack<int> numIndexesToRemove = new Stack<int>();
        for (int i = 0; i < numList.Count; i++)
        {
            if (denList.Remove(numList[i]))
            {
                numIndexesToRemove.Push(i);
            }
        }

        while (numIndexesToRemove.Count > 0)
        {
            numList.RemoveAt(numIndexesToRemove.Pop());
        }

        switch (denList.Count)
        {
            case 0:
                switch (numList.Count)
                {
                    case 0:
                        return AbstractClass.Implementation1;
                    case 1:
                        return numList.First<Implementation2>();
                    default:
                        return new Implementation3(numList.OfType<AbstractClass>());
                }
            case 1:
                switch (numList.Count)
                {
                    case 0:
                        return new Implementation4(AbstractClass.Implementation1, denList.First<Implementation2>());
                    case 1:
                        return new Implementation4(numList.First<Implementation2>(), denList.First<Implementation2>());
                    default:
                        return new Implementation4(new Implementation3(numList.OfType<AbstractClass>()), denList.First<Implementation2>());
                }
            default:
                switch (numList.Count)
                {
                    case 0:
                        return new Implementation4(AbstractClass.Implementation1, new Implementation3(denList.OfType<AbstractClass>()));
                    case 1:
                        return new Implementation4(numList.First<Implementation2>(), new Implementation3(denList.OfType<AbstractClass>()));
                    default:
                        return new Implementation4(new Implementation3(numList.OfType<AbstractClass>()), new Implementation3(denList.OfType<AbstractClass>()));
                }
        }
    }

    public bool Equals(Implementation4 other)
    {
        return Numerator.Equals(other.Numerator) && Denominator.Equals(other.Denominator);
    }
}

Сердце того, что я пытаюсь проверить, - это Distill метод, который, как вы можете видеть, может работать рекурсивно. Потому что нож AbstractClass бессмысленно в этой парадигме, она нарушает логику алгоритма. Даже пытаться проверить класс с заглушкой несколько бесполезно, поскольку я мало что могу с этим поделать, кроме как сгенерировать исключение или сделать вид, что это экземпляр Implementation1, Я бы предпочел не переписывать тестируемый код таким образом, чтобы приспособиться к конкретной среде тестирования, а писать сам тест таким образом, чтобы никогда не заглушать AbstractClass это то, что я пытаюсь сделать здесь.

Я надеюсь, что очевидно, чем то, что я делаю, отличается, например, от конструкции типа enum. Кроме того, я анонимизировал объекты для публикации здесь (как вы можете сказать), и я не включил все методы, поэтому, если вы собираетесь комментировать, чтобы сказать мне, что Implementation4.Equals(Implementation4) сломан, не волнуйтесь, я знаю, что он здесь сломан, но мой реальный код решает эту проблему.

Другое редактирование:

Вот пример одного из заводских классов. Он находится в каталоге фабрики тестового проекта, созданного Pex.

public static partial class Implementation3Factory
{
    [PexFactoryMethod(typeof(Implementation3))]
    public static Implementation3 Create(IEnumerable<AbstractClass> instances, bool useEmptyConstructor)
    {
        Implementation3 i3 = null;
        if (useEmptyConstructor)
        {
            i3 = new Implementation3();
        }
        else
        {
            i3 = new Implementation3(instances);
        }

        return i3;
    }
}

В моих фабричных методах для этих конкретных реализаций можно использовать любой конструктор для создания конкретной реализации. В примере useEmptyConstructor Параметр определяет, какой конструктор использовать. Другие фабричные методы имеют аналогичные особенности. Я вспоминаю чтение, хотя я не могу сразу найти ссылку, что эти фабричные методы должны позволять создавать объект в любой возможной конфигурации.

1 ответ

Вы пытались рассказать Пекс, используя [PexUseType] атрибут, что неабстрактные подтипы для вашего абстрактного класса существуют? Если Pex не знает ни о каких-либо неабстрактных подтипах, тогда решатель ограничений Pex определит, что путь кода, который зависит от существования неабстрактного подтипа, недопустим.

Другие вопросы по тегам