简体   繁体   English

如何告诉Pex不要存根具有具体实现的抽象类

[英]How to tell Pex not to stub an abstract class that has concrete implementations

I'm trying to use Pex to test some code. 我正在尝试使用Pex来测试一些代码。 I have an abstract class with four concrete implementations. 我有一个抽象类,有四个具体的实现。 I have created factory methods for each of the four concrete types. 我为四种具体类型中的每一种创建了工厂方法。 I had also created one for the abstract type, except as this nice thread explains, Pex will not use the abstract factory method, nor should it. 我还为抽象类型创建了一个,除非这个好的线程解释,Pex不会使用抽象工厂方法,也不应该使用它。

The problem is that some of my code depends on the four concrete types being all there are (since it is very, very unlikely that any more subclasses will be created), but Pex is breaking the code by using Moles to create a stub. 问题是我的一些代码依赖于四种具体类型(因为创建更多的子类非常非常不可能),但是Pex通过使用Moles来创建存根来破坏代码。

How can I force Pex to use one of the factory methods (any one, I don't care) to create instances of the abstract class without ever creating Moles stubs for that abstract class? 我怎样才能强制Pex使用其中一个工厂方法(任何一个,我不关心)来创建抽象类的实例而不为该抽象类创建Moles存根? Is there a PexAssume directive that will accomplish this? 是否有PexAssume指令可以完成此任务? Note that some of the concrete types form a type of tree structure, so say ConcreteImplementation derives from AbstractClass , and ConcreteImplementation has two properties of type AbstractClass . 请注意,某些具体类型构成一种树结构,因此, ConcreteImplementation派生自AbstractClass ,而ConcreteImplementation具有AbstractClass类型的两个属性。 I need to ensure that no stubs are used anywhere in the tree at all. 我需要确保树中的任何地方都没有使用存根。 (Not all the concrete implementations have AbstractClass properties.) (并非所有具体实现都具有AbstractClass属性。)

Edit: 编辑:

It appears that I need to add some more information on how the class structure itself works, though remember that the goal is still how to get Pex not to stub classes. 看来我需要添加一些关于类结构本身如何工作的更多信息,但要记住,目标仍然是如何让Pex不要使用存根类。

Here are simplified versions of the abstract base class and the four concrete implementations thereof. 以下是抽象基类的简化版本及其四个具体实现。

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);
    }
}

The heart of what I am trying to test is the Distill method, which as you can see has the potential to run recursively. 我试图测试的核心是Distill方法,你可以看到它有可能递归运行。 Because a stubbed AbstractClass is meaningless in this paradigm, it breaks the algorithm logic. 因为在这个范例中,存根的AbstractClass是没有意义的,所以它打破了算法逻辑。 Even trying to test for a stubbed class is somewhat useless, since there is little I can do about it other than throw an exception or pretend that it is an instance of Implementation1 . 即使尝试测试存根类也有些无用,因为除了抛出异常或假装它是Implementation1的实例之外,我几乎无能为力。 I would prefer not to have to rewrite the code under test to accommodate a specific testing framework in that way, but writing the test itself in such a way as never to stub AbstractClass is what I am trying to do here. 我宁愿不必重写被测试的代码以便以这种方式容纳特定的测试框架,但是编写测试本身的方式永远不会存在于AbstractClass是我在这里尝试做的。

I hope it is apparent how what I am doing differs from a type-safe enum construct, for instance. 我希望很明显我所做的事情与类型安全的枚举构造有什么不同。 Also, I anonymized objects for posting here (as you can tell), and I did not include all methods, so if you're going to comment to tell me that Implementation4.Equals(Implementation4) is broken, don't worry, I'm aware that it is broken here, but my actual code takes care of the issue. 另外,我匿名化了在这里发布的对象(你可以告诉),而且我没有包含所有方法,所以如果你要评论告诉我Implementation4.Equals(Implementation4)已经坏了,不用担心,我我知道它在这里被打破了,但是我的实际代码会解决这个问题。

Another edit: 另一个编辑:

Here is an example of one of the factory classes. 以下是其中一个工厂类的示例。 It is in the Factories directory of the Pex-generated test project. 它位于Pex生成的测试项目的Factories目录中。

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;
    }
}

In my factory methods for these concrete implementations, it is possible to use any constructor to create the concrete implementation. 在我的工厂方法中,可以使用任何构造函数来创建具体实现。 In the example, the useEmptyConstructor parameter controls which constructor to use. 在该示例中, useEmptyConstructor参数控制要使用的构造函数。 The other factory methods have similar features. 其他工厂方法具有类似的功能。 I recall reading, though I cannot immediately find the link, that these factory methods should allow the object to be created in every possible configuration. 我记得读过,虽然我无法立即找到链接,但这些工厂方法应该允许在每个可能的配置中创建对象。

Have you tried telling Pex using the [PexUseType] attribute, that non-abstract subtypes for your abstract class exist? 您是否尝试使用[PexUseType]属性告诉Pex,您的抽象类的非抽象子类型是否存在? If Pex is not aware of any non-abstract subtypes, then Pex's constraint solver would determine that a code path that depends on the existence of a non-abstract subtype is infeasible. 如果Pex不知道任何非抽象子类型,那么Pex的约束求解器将确定依赖于非抽象子类型存在的代码路径是不可行的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM