How to tell Pex not to stub an abstract class that has concrete implementations
Asked Answered
A

1

61

I'm trying to use Pex to test some code. 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.

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.

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? Is there a PexAssume directive that will accomplish this? 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. I need to ensure that no stubs are used anywhere in the tree at all. (Not all the concrete implementations have AbstractClass properties.)

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.

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. Because a stubbed AbstractClass is meaningless in this paradigm, it breaks the algorithm logic. 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. 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.

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.

Another edit:

Here is an example of one of the factory classes. It is in the Factories directory of the Pex-generated test project.

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. 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.

Axiomatic answered 21/9, 2011 at 18:49 Comment(10)
Not sure what problem you are solving with that implementation, but if anyone ever creates another type derived from the base class, then it sounds like they would also break your implementation. That sounds like it could both break extensibility and surprise the user, both of which are design smells. Can you instead add an attribute (possibly internal) to your derived classes, and simply search for that? Then you don't have to care that PEX creates a stub, since you don't have to use it, and it won't be annotated in a way that causes your code to break. It also won't break user code.Artisan
@MerlynMorgan-Graham Thanks for your input. In truth, this project is more suited to F# than C#, but future maintainability is a concern. The behavior is closer to "discriminated union" than to true inheritance. That said, the four subclasses of the abstract base class represent a closed set of operations within a calculation structure I have set up. No one is going to extend this, but both the abstract base class and the concrete subclasses need to be visible outside their assembly. If there is something else you mean by internal, I'm not sure what it is.Axiomatic
If only those for derived classes make sense, then why worry - Will it actually break something? If so, how do you detect the derived classes exist? I was trying to provide an alternative for your detection mechanism. Also, you seem to have a pattern similar to a type-safe enum. You could follow that pattern completely and make all your implementations internal, and just make static factory properties on the base class for the four implementations. Name them properly so they create the right type, but return them as the base type.Artisan
@MerlynMorgan-Graham Thanks for your comment. I think I'm going to need to add some information to the question when I have a few minutes to do so (that is, not today, unfortunately). The stub class does break things during testing because some assumptions are made within certain methods of each class (that operate recursively) that the four derived classes form the complete set of operations. This isn't quite a type-safe enum because the derived classes implement abstract methods. There's a little more to it, but again, I think I need to edit the question.Axiomatic
@Andrew: Did you consider creating a class factory, [PexClassFactory] and have that be a class implementing the abstract one in whatever ways you want to test.Greeting
@Greeting I see very little documentation (one page in Google) that mentions PexClassFactoryAttribute, and it appears to decorate a non-static class with a static factory method decorated with PexFactoryMethodAttribute. Does this provide functionality that PexFactoryMethodAttribute alone does not? I am already using [PexFactoryMethod] on factory classes for the implementations, and as the link I gave in my question describes, [PexFactoryMethod(typeof(AbstractClass))] will not work (I tried it anyway and confirmed that Pex will not use it).Axiomatic
PexFactoryMethod will instantiate a stub class and only take your implementation of that said function/method ( stub the rest of the virtual functions). PexFactorClass overrides that and gives you control and should help you stop Pex from adding the extra stubs.Greeting
@Greeting I'm a little confused here, I'll admit. Do you have a link to documentation on this? Also, I'll edit to post a representative factory method in case I wasn't clear.Axiomatic
@Andrew: Pex does disable the "PexFactoryClass" property from an object if the latter is a "PexFactoryMethod". I suggest you comment out either or and go on trial and error until you have a small test class to prove the inner workings of Pex. They modify these properties from update to update (causes crashes as Pex tries to build a class from an abstract, and the coder wants to add his/her own and so on).Greeting
Someone please post an answer that somehow helps the question so that I can reward the bountyDroopy
T
1

Have you tried telling Pex using the [PexUseType] attribute, that non-abstract subtypes for your abstract class exist? 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.

Tuyettv answered 28/11, 2011 at 21:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.