如何告诉Pex不要存根具体实现的抽象类
我正在尝试使用Pex来testing一些代码。 我有一个具有四个具体实现的抽象类。 我已经为四种具体types中的每一种创build了工厂方法。 我也为抽象types创build了一个,除非这个漂亮的线程解释,Pex将不会使用抽象工厂方法,也不应该使用抽象工厂方法。
问题是我的一些代码依赖于所有的四个具体types(因为这是非常非常不可能的,将有更多的子类将被创build),但Pex是通过使用Moles来创build一个存根破坏代码。
我如何强迫Pex使用工厂方法之一(我不介意的任何一种方法)创build抽象类的实例,而不必为该抽象类创buildMoles存根? 有一个PexAssume
指令可以完成这个吗? 请注意,某些具体types构成了一种树结构types,所以说ConcreteImplementation
从AbstractClass
派生,而ConcreteImplementation
具有两个AbstractClass
types的属性。 我需要确保根本不在树的任何地方使用存根。 (并不是所有的具体实现都具有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<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); } }
我试图testing的核心是Distill
方法,你可以看到它有recursion运行的潜力。 因为AbstractClass
在这个范例中是没有意义的,所以它破坏了algorithm逻辑。 即使试图testing一个存根类也是没有用的,因为除了抛出一个exception,或者假设它是Implementation1
一个实例之外,我几乎不能做任何事情。 我宁愿不必重写被testing的代码,以适应特定的testing框架,但是以这样的方式编写testing本身,从不存在AbstractClass
是我在这里要做的。
例如,我希望显而易见的是,我所做的不同于types安全的枚举构造。 此外,我匿名化的对象在这里张贴(你可以告诉),我没有包括所有的方法,所以如果你要评论告诉我, Implementation4.Equals(Implementation4)
已经坏了,别担心,我我意识到它在这里被打破,但我的实际代码照顾这个问题。
另一个编辑:
这是一个工厂类的例子。 它位于Pex生成的testing项目的工厂目录中。
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; } }
在这些具体实现的工厂方法中,可以使用任何构造函数来创build具体实现。 在这个例子中, useEmptyConstructor
参数控制使用哪个构造函数。 其他工厂方法具有相似的特征。 我记得阅读,虽然我不能立即find链接,这些工厂方法应该允许在每个可能的configuration创build对象。
您是否试过使用[PexUseType]
属性告诉Pex,抽象类的非抽象子types是否存在? 如果Pex不知道任何非抽象的子types,那么Pex的约束求解器将确定取决于非抽象子types存在的代码path是不可行的。