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

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

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

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

Edit:

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

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

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

Вот пример одного из классов factory. Он находится в каталоге Factorys тестового проекта, созданного 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;
    }
}

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

Ответ 1

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