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

Я пытаюсь использовать 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
{
    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
{
    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
{
    public IEnumerable Instances { get; private set; }

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

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

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

        Instances = instances;
    }

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

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

        if (newInstances.OfType().Any())
        {
            List denominator = new List();

            while (newInstances.OfType().Any())
            {
                denominator.AddRange(newInstances.OfType().Select(c => c.Denominator));
                newInstances = newInstances.Where(c => c.GetType() != typeof(Implementation4))
                                           .Concat(newInstances.OfType().Select(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 i2s = newInstances.Select(c => c.Distill()).OfType();
        switch (i2s.Count())
        {
            case 0:
                return AbstractClass.Implementation1;
            case 1:
                return i2s.First();
            default:
                return new Implementation3(i2s.OrderBy(c => c.Name).Select(c => c));
        }
    }

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

public class Implementation4 : AbstractClass, IEquatable
{
    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(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(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 numList =
            numDistilled.GetType() == typeof(Implementation2) ? new List(1) { ((Implementation2)numDistilled) } : new List(((Implementation3)numDistilled).Instances.OfType());

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

        Stack numIndexesToRemove = new Stack();
        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();
                    default:
                        return new Implementation3(numList.OfType());
                }
            case 1:
                switch (numList.Count)
                {
                    case 0:
                        return new Implementation4(AbstractClass.Implementation1, denList.First());
                    case 1:
                        return new Implementation4(numList.First(), denList.First());
                    default:
                        return new Implementation4(new Implementation3(numList.OfType()), denList.First());
                }
            default:
                switch (numList.Count)
                {
                    case 0:
                        return new Implementation4(AbstractClass.Implementation1, new Implementation3(denList.OfType()));
                    case 1:
                        return new Implementation4(numList.First(), new Implementation3(denList.OfType()));
                    default:
                        return new Implementation4(new Implementation3(numList.OfType()), new Implementation3(denList.OfType()));
                }
        }
    }

    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 instances, bool useEmptyConstructor)
    {
        Implementation3 i3 = null;
        if (useEmptyConstructor)
        {
            i3 = new Implementation3();
        }
        else
        {
            i3 = new Implementation3(instances);
        }

        return i3;
    }
}

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

59
задан Andrew 28 November 2011 в 19:12
поделиться