2011-09-21 23 views
59

Tôi đang cố gắng sử dụng Pex để kiểm tra một số mã. Tôi có một lớp trừu tượng với bốn triển khai cụ thể. Tôi đã tạo ra các phương pháp nhà máy cho từng loại bê tông. Tôi cũng đã tạo một kiểu cho kiểu trừu tượng, trừ khi this nice thread giải thích, Pex sẽ không sử dụng phương thức trừu tượng của nhà máy, cũng không nên.Làm thế nào để nói với Pex không để cho một lớp trừu tượng có triển khai cụ thể

Vấn đề là một số mã của tôi phụ thuộc vào bốn loại cụ thể là (vì rất, rất khó có thể tạo thêm lớp con), nhưng Pex đang phá vỡ mã bằng cách sử dụng nốt ruồi để tạo một cuống.

Làm cách nào tôi có thể buộc Pex sử dụng một trong các phương pháp nhà máy (bất kỳ phương pháp nào, tôi không quan tâm) để tạo các phiên bản của lớp trừu tượng mà không bao giờ tạo các nốt Moles cho lớp trừu tượng đó? Có chỉ thị PexAssume sẽ thực hiện việc này không? Lưu ý rằng một số loại bê tông tạo thành một loại cấu trúc cây, vì vậy hãy nói ConcreteImplementation có nguồn gốc từ AbstractClassConcreteImplementation có hai thuộc tính loại AbstractClass. Tôi cần phải đảm bảo rằng không có cuống được sử dụng bất cứ nơi nào trong cây ở tất cả. (Không phải tất cả việc triển khai bê tông có AbstractClass tài sản.)

Edit:

Dường như tôi cần phải thêm một số chi tiết thông tin về cách cấu trúc lớp bản thân công trình, mặc dù nhớ rằng mục tiêu vẫn là làm thế nào để khiến Pex không được học các lớp.

Dưới đây là các phiên bản đơn giản của lớp cơ sở trừu tượng và bốn triển khai cụ thể của chúng.

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

Trung tâm của những gì tôi đang thử nghiệm là phương pháp Distill, như bạn có thể thấy có khả năng chạy đệ quy. Bởi vì AbstractClass bị bướng bỉnh là vô nghĩa trong mô hình này, nó phá vỡ logic thuật toán. Ngay cả cố gắng để kiểm tra cho một lớp học stubbed là hơi vô dụng, vì có rất ít tôi có thể làm về nó khác hơn là ném một ngoại lệ hoặc giả vờ rằng nó là một ví dụ của Implementation1. Tôi không muốn viết lại đoạn mã đang được thử nghiệm để chứa một khuôn khổ thử nghiệm cụ thể theo cách đó, nhưng viết bản thân bài kiểm tra theo cách như vậy không bao giờ để khai quật AbstractClass là những gì tôi đang cố gắng làm ở đây.

Tôi hy vọng nó là rõ ràng như thế nào những gì tôi đang làm khác với một loại anum enum xây dựng, ví dụ. Ngoài ra, tôi ẩn danh các đối tượng để đăng ở đây (như bạn có thể nói), và tôi không bao gồm tất cả các phương pháp, vì vậy nếu bạn định bình luận cho tôi biết rằng Implementation4.Equals(Implementation4) bị hỏng, đừng lo, tôi biết rằng bị hỏng ở đây, nhưng mã thực sự của tôi sẽ giải quyết vấn đề.

Một biên tập:

Dưới đây là một ví dụ về một trong các lớp nhà máy. Nó nằm trong thư mục Nhà máy của dự án thử nghiệm do Pex tạo ra.

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

Trong phương pháp nhà máy của tôi để triển khai cụ thể, có thể sử dụng bất kỳ nhà xây dựng nào để tạo triển khai cụ thể. Trong ví dụ này, kiểm soát thông số useEmptyConstructor sử dụng hàm tạo nào. Các phương pháp nhà máy khác có các tính năng tương tự. Tôi nhớ lại việc đọc, mặc dù tôi không thể tìm thấy liên kết ngay lập tức, rằng các phương thức nhà máy này sẽ cho phép đối tượng được tạo ra trong mọi cấu hình có thể.

+2

Không chắc chắn bạn đang giải quyết vấn đề gì với việc triển khai đó, nhưng nếu có ai tạo ra loại khác có nguồn gốc từ lớp cơ sở, thì có vẻ như họ cũng sẽ phá vỡ việc triển khai của bạn. Nghe có vẻ như nó có thể phá vỡ khả năng mở rộng và gây ngạc nhiên cho người dùng, cả hai đều là mùi thiết kế. Thay vào đó bạn có thể thêm một thuộc tính (có thể là 'nội bộ') vào các lớp dẫn xuất của bạn và chỉ cần tìm kiếm nó? Sau đó, bạn không cần phải quan tâm rằng PEX tạo ra một nhánh, vì bạn không phải sử dụng nó, và nó sẽ không được chú thích theo cách làm cho mã của bạn bị hỏng. Nó cũng sẽ không phá vỡ mã người dùng. –

+0

@ MerlynMorgan-Graham Cảm ơn bạn đã nhập. Trong sự thật, dự án này phù hợp hơn với F # hơn C#, nhưng khả năng bảo trì trong tương lai là một mối quan tâm. Hành vi này gần gũi hơn với "công đoàn phân biệt đối xử" hơn là thừa kế thực sự. Điều đó nói rằng, bốn lớp con của lớp cơ sở trừu tượng đại diện cho một tập hợp các hoạt động khép kín trong một cấu trúc tính toán mà tôi đã thiết lập. Không ai sẽ mở rộng điều này, nhưng cả lớp cơ sở trừu tượng và các lớp con cụ thể cần được hiển thị bên ngoài hội đồng của chúng. Nếu có một cái gì đó khác bạn có nghĩa là bởi _internal_, tôi không chắc chắn nó là gì. – Andrew

+0

Nếu chỉ những người cho các lớp học có nguồn gốc có ý nghĩa, thì tại sao lo lắng - Nó sẽ thực sự * phá vỡ * một cái gì đó? Nếu vậy, làm thế nào để bạn phát hiện các lớp dẫn xuất tồn tại? Tôi đã cố gắng cung cấp một thay thế cho cơ chế phát hiện của bạn. Ngoài ra, bạn dường như có một mô hình tương tự như một enum loại an toàn. Bạn có thể làm theo mô hình đó hoàn toàn và làm cho tất cả các triển khai của bạn bên trong, và chỉ tạo các thuộc tính nhà máy tĩnh trên lớp cơ sở cho bốn triển khai thực hiện. Đặt tên chúng đúng cách để chúng tạo đúng loại, nhưng trả lại chúng làm kiểu cơ sở. –

Trả lời

1

Bạn đã thử nói với Pex sử dụng thuộc tính [PexUseType], các kiểu con không trừu tượng cho lớp trừu tượng của bạn có tồn tại không? Nếu Pex không nhận thức được bất kỳ kiểu con không trừu tượng nào, thì trình giải quyết hạn chế của Pex sẽ xác định rằng một đường dẫn mã phụ thuộc vào sự tồn tại của một kiểu con không trừu tượng là không khả thi.

Các vấn đề liên quan