Как сказать Pex не заглушать абстрактный класс, который имеет конкретные реализации - PullRequest
59 голосов
/ 21 сентября 2011

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

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

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

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

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

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

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) не работает, не волнуйтесь, я в курсечто он здесь не работает, но мой реальный код решает эту проблему.

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

Вот пример одного из фабричных классов.Он находится в каталоге Factories сгенерированного 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 Ответ

1 голос
/ 29 ноября 2011

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

...