2011-09-21 95 views
59

我想用Pex來測試一些代碼。我有一個具有四個具體實現的抽象類。我已經爲四種具體類型中的每一種創建了工廠方法。我也爲抽象類型創建了一個,除了this nice thread解釋,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<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就是我在這裏要做的。

我希望很明顯,我所做的與例如類型安全的枚舉構造不同。另外,我匿名發佈的對象(如你所知),並且我沒有包含所有的方法,所以如果你要評論告訴我Implementation4.Equals(Implementation4)已損壞,請不要擔心,我知道它在這裏被打破,但我的實際代碼照顧這個問題。

另一個編輯:

這裏是工廠類之一的實例。它位於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參數控制要使用哪個構造函數。其他工廠方法具有相似的功能。我記得閱讀,雖然我不能立即找到鏈接,這些工廠方法應該允許在每種可能的配置中創建對象。

+2

不知道你用這個實現解決了什麼問題,但是如果有人創建了另一個從基類派生的類型,那麼它聽起來像他們也會破壞你的實現。這聽起來像可能會破壞擴展性並給用戶帶來驚喜,而這兩者都是設計氣味。你可以添加一個屬性(可能'內部')到你的派生類,並簡單地搜索?然後,您不必關心PEX創建存根,因爲您不必使用它,並且不會以導致您的代碼中斷的方式進行註釋。它也不會破壞用戶代碼。 –

+0

@ MerlynMorgan-Graham感謝您的意見。事實上,這個項目比C#更適合F#,但未來的可維護性是一個問題。這種行爲更接近「有區別的聯盟」,而不是真正的繼承。也就是說,抽象基類的四個子類表示我設置的計算結構中的一組封閉操作。沒有人會擴展它,但抽象基類和具體子類都需要在其程序集外部可見。如果你有什麼其他的意思是_internal_,我不確定它是什麼。 – Andrew

+0

如果只有那些派生類是有意義的,那麼爲什麼要擔心 - 它會實際上破壞某些東西嗎?如果是這樣,你如何檢測派生類存在?我試圖爲您的檢測機制提供替代方案。此外,你似乎有一個模式類似於類型安全的枚舉。您可以完全遵循該模式,並將所有實現內部化,並在四個實現的基類上創建靜態工廠屬性。正確命名它們,以便它們創建正確的類型,但將它們作爲基本類型返回。 –

回答

1

您是否嘗試過使用[PexUseType]屬性告訴Pex,抽象類的非抽象子類型是否存在?如果Pex不知道任何非抽象子類型,Pex的約束求解器將確定取決於非抽象子類型存在的代碼路徑是不可行的。