Làm thế nào để đơn vị kiểm tra các lớp trừu tượng: mở rộng với sơ khai?


446

Tôi đã tự hỏi làm thế nào để đơn vị kiểm tra các lớp trừu tượng và các lớp mở rộng các lớp trừu tượng.

Tôi có nên kiểm tra lớp trừu tượng bằng cách mở rộng nó, loại bỏ các phương thức trừu tượng và sau đó kiểm tra tất cả các phương thức cụ thể không? Sau đó, chỉ kiểm tra các phương thức tôi ghi đè và kiểm tra các phương thức trừu tượng trong các bài kiểm tra đơn vị cho các đối tượng mở rộng lớp trừu tượng của tôi?

Tôi có nên có một trường hợp thử nghiệm trừu tượng có thể được sử dụng để kiểm tra các phương thức của lớp trừu tượng và mở rộng lớp này trong trường hợp thử nghiệm của tôi cho các đối tượng mở rộng lớp trừu tượng không?

Lưu ý rằng lớp trừu tượng của tôi có một số phương pháp cụ thể.

Câu trả lời:


268

Viết một đối tượng Mock và sử dụng chúng chỉ để thử nghiệm. Chúng thường rất rất rất tối thiểu (kế thừa từ lớp trừu tượng) và không nhiều hơn. Sau đó, trong Bài kiểm tra đơn vị của bạn, bạn có thể gọi phương thức trừu tượng mà bạn muốn kiểm tra.

Bạn nên kiểm tra lớp trừu tượng có chứa một số logic như tất cả các lớp khác mà bạn có.


9
Chết tiệt, tôi phải nói rằng đây là lần đầu tiên tôi đồng ý với ý tưởng sử dụng một bản giả.
Jonathan Allen

5
Bạn cần hai lớp, một giả và kiểm tra. Lớp giả chỉ mở rộng các phương thức trừu tượng của lớp Trừu tượng đang thử nghiệm. Những phương thức đó có thể là không, trả về null, v.v. vì chúng sẽ không được kiểm tra. Lớp kiểm tra chỉ kiểm tra API công cộng không trừu tượng (tức là Giao diện được triển khai bởi lớp Trừu tượng). Đối với bất kỳ lớp nào mở rộng lớp Trừu tượng, bạn sẽ cần các lớp kiểm tra bổ sung vì các phương thức trừu tượng không được đề cập.
nhà sư mạng

10
Rõ ràng là có thể làm điều này .. nhưng để thực sự kiểm tra bất kỳ lớp dẫn xuất nào, bạn sẽ kiểm tra chức năng cơ sở này nhiều lần .. điều này dẫn đến việc bạn có một bản kiểm tra trừu tượng để bạn có thể tìm ra sự trùng lặp này trong kiểm tra. Tất cả điều này có mùi! Tôi thực sự khuyên bạn nên xem xét lại lý do tại sao bạn đang sử dụng các lớp trừu tượng ở nơi đầu tiên và xem liệu cái gì khác sẽ hoạt động tốt hơn.
Nigel Thorne

5
câu trả lời tiếp theo quá nhiều là tốt hơn nhiều.
Martin Spamer

22
@MartiSpamer: Tôi sẽ không nói câu trả lời này được đánh giá quá cao vì nó được viết sớm hơn nhiều (2 năm) so với câu trả lời bạn cho là tốt hơn dưới đây. Chúng ta hãy khuyến khích Patrick vì trong bối cảnh khi anh ấy đăng câu trả lời này, nó thật tuyệt. Hãy khuyến khích nhau. Chúc mừng
Marvin Thobejane

449

Có hai cách trong đó các lớp cơ sở trừu tượng được sử dụng.

  1. Bạn đang chuyên về đối tượng trừu tượng của mình, nhưng tất cả các máy khách sẽ sử dụng lớp dẫn xuất thông qua giao diện cơ sở của nó.

  2. Bạn đang sử dụng một lớp cơ sở trừu tượng để tạo ra sự trùng lặp trong các đối tượng trong thiết kế của mình và khách hàng sử dụng các triển khai cụ thể thông qua các giao diện của riêng họ.!


Giải pháp cho 1 - Mô hình chiến lược

Lựa chọn 1

Nếu bạn có tình huống đầu tiên, thì bạn thực sự có một giao diện được xác định bởi các phương thức ảo trong lớp trừu tượng mà các lớp dẫn xuất của bạn đang thực hiện.

Bạn nên xem xét việc biến nó thành một giao diện thực sự, thay đổi lớp trừu tượng của bạn thành cụ thể và lấy một thể hiện của giao diện này trong hàm tạo của nó. Các lớp dẫn xuất của bạn sau đó trở thành triển khai của giao diện mới này.

Vô cảm

Điều này có nghĩa là bây giờ bạn có thể kiểm tra lớp trừu tượng trước đây của mình bằng cách sử dụng một phiên bản giả của giao diện mới và mỗi lần thực hiện mới thông qua giao diện công khai. Mọi thứ đều đơn giản và có thể kiểm chứng.


Giải pháp cho 2

Nếu bạn có tình huống thứ hai, thì lớp trừu tượng của bạn đang hoạt động như một lớp trợ giúp.

Tóm tắt

Hãy xem chức năng của nó. Xem nếu bất kỳ của nó có thể được đẩy lên các đối tượng đang được thao tác để giảm thiểu sự trùng lặp này. Nếu bạn vẫn còn bất cứ thứ gì, hãy xem làm cho nó trở thành một lớp trợ giúp mà việc triển khai cụ thể của bạn có trong hàm tạo của chúng và loại bỏ lớp cơ sở của chúng.

Người trợ giúp xe máy

Điều này một lần nữa dẫn đến các lớp cụ thể đơn giản và dễ kiểm tra.


Như một quy luật

Ưu tiên mạng phức tạp của các đối tượng đơn giản trên một mạng đơn giản của các đối tượng phức tạp.

Chìa khóa để mở rộng mã có thể kiểm tra là các khối xây dựng nhỏ và hệ thống dây độc lập.


Cập nhật: Làm thế nào để xử lý hỗn hợp của cả hai?

Có thể có một lớp cơ sở thực hiện cả hai vai trò này ... tức là: nó có giao diện chung và có các phương thức trợ giúp được bảo vệ. Nếu đây là trường hợp, thì bạn có thể tính ra các phương thức của trình trợ giúp thành một lớp (kịch bản 2) và chuyển đổi cây thừa kế thành một mẫu chiến lược.

Nếu bạn thấy bạn có một số phương thức mà lớp cơ sở của bạn thực hiện trực tiếp và các phương thức khác là ảo, thì bạn vẫn có thể chuyển đổi cây thừa kế thành một mẫu chiến lược, nhưng tôi cũng sẽ coi đó là một chỉ báo tốt rằng các trách nhiệm không được căn chỉnh chính xác và có thể cần tái cấu trúc.


Cập nhật 2: Các lớp trừu tượng như một bước đệm (2014/06/12)

Tôi đã có một tình huống vào ngày khác khi tôi sử dụng trừu tượng, vì vậy tôi muốn khám phá lý do tại sao.

Chúng tôi có một định dạng tiêu chuẩn cho các tập tin cấu hình của chúng tôi. Công cụ đặc biệt này có 3 tệp cấu hình tất cả ở định dạng đó. Tôi muốn một lớp được gõ mạnh cho mỗi tệp cài đặt vì vậy, thông qua việc tiêm phụ thuộc, một lớp có thể yêu cầu các cài đặt mà nó quan tâm.

Tôi đã thực hiện điều này bằng cách có một lớp cơ sở trừu tượng biết cách phân tích các định dạng tệp cài đặt và các lớp dẫn xuất tiếp xúc với các phương thức đó, nhưng đóng gói vị trí của tệp cài đặt.

Tôi có thể đã viết một "SettingsFileParser" rằng 3 lớp được bao bọc, và sau đó được ủy quyền cho lớp cơ sở để hiển thị các phương thức truy cập dữ liệu. Tôi quyết định không làm điều này nhưng vì nó sẽ dẫn đến 3 các lớp thừa kế với nhiều đoàn đại biểu mã trong họ hơn bất cứ điều gì khác.

Tuy nhiên ... khi mã này phát triển và người tiêu dùng của mỗi lớp cài đặt này trở nên rõ ràng hơn. Mỗi cài đặt người dùng sẽ yêu cầu một số cài đặt và chuyển đổi chúng theo một cách nào đó (vì cài đặt là văn bản, họ có thể gói chúng trong các đối tượng chuyển đổi chúng thành số, v.v.). Khi điều này xảy ra, tôi sẽ bắt đầu trích xuất logic này vào các phương thức thao tác dữ liệu và đẩy chúng trở lại các lớp cài đặt được gõ mạnh. Điều này sẽ dẫn đến một giao diện cấp cao hơn cho mỗi bộ cài đặt, mà cuối cùng không còn biết nó đang xử lý 'cài đặt'.

Tại thời điểm này, các lớp cài đặt được gõ mạnh sẽ không còn cần các phương thức "getter" để lộ triển khai 'cài đặt' bên dưới.

Tại thời điểm đó tôi sẽ không còn muốn giao diện chung của chúng bao gồm các phương thức truy cập cài đặt; vì vậy tôi sẽ thay đổi lớp này để đóng gói một trình phân tích cú pháp cài đặt thay vì xuất phát từ nó.

Do đó, lớp Trừu tượng là: một cách để tôi tránh mã ủy nhiệm tại thời điểm này và một điểm đánh dấu trong mã để nhắc tôi thay đổi thiết kế sau này. Tôi có thể không bao giờ nhận được nó, vì vậy nó có thể sống tốt trong khi ... chỉ có mã mới có thể biết.

Tôi thấy điều này đúng với bất kỳ quy tắc nào ... như "không có phương thức tĩnh" hoặc "không có phương thức riêng tư". Chúng chỉ ra một mùi trong mã ... và điều đó thật tốt. Nó giúp bạn tìm kiếm sự trừu tượng mà bạn đã bỏ lỡ ... và cho phép bạn tiếp tục cung cấp giá trị cho khách hàng của mình trong thời gian đó.

Tôi tưởng tượng các quy tắc như thế này xác định một cảnh quan, nơi mã duy trì sống trong các thung lũng. Khi bạn thêm hành vi mới, nó giống như mưa rơi vào mã của bạn. Ban đầu bạn đặt nó ở bất cứ nơi nào nó hạ cánh .. sau đó bạn tái cấu trúc để cho phép các lực lượng có thiết kế tốt đẩy hành vi xung quanh cho đến khi tất cả kết thúc ở các thung lũng.


18
Đây là một câu trả lời tuyệt vời. Tốt hơn nhiều so với đánh giá hàng đầu. Nhưng sau đó tôi đoán chỉ những người thực sự muốn viết mã có thể kiểm tra mới đánh giá cao nó .. :)
MalcomTucker

22
Tôi không thể hiểu được câu trả lời này thực sự tốt như thế nào. Nó hoàn toàn thay đổi cách nghĩ của tôi về các lớp trừu tượng. Cảm ơn Nigel.
MalcomTucker

4
Ôi không .. một nguyên lý khác tôi phải gồng mình suy nghĩ lại! Cảm ơn (cả hai đều mỉa mai và không mỉa mai vì một lần tôi đã đồng hóa nó và cảm thấy như một lập trình viên giỏi hơn)
Martin Lyne

11
Câu trả lời tốt đẹp. Chắc chắn có điều gì đó để suy nghĩ về ... nhưng không phải những gì bạn đang nói về cơ bản sôi sục để không sử dụng các lớp trừu tượng?
brianestey

32
+1 cho riêng Quy tắc, "Ưu tiên mạng phức tạp của các đối tượng đơn giản trên mạng đơn giản của các đối tượng phức tạp."
David Glass

12

Những gì tôi làm cho các lớp và giao diện trừu tượng là như sau: Tôi viết một bài kiểm tra, sử dụng đối tượng vì nó cụ thể. Nhưng biến loại X (X là lớp trừu tượng) không được đặt trong thử nghiệm. Lớp thử nghiệm này không được thêm vào bộ thử nghiệm, mà là các lớp con của nó, có một phương thức thiết lập để đặt biến thành một triển khai cụ thể của X. Bằng cách đó tôi không sao chép mã thử nghiệm. Các lớp con của kiểm tra không được sử dụng có thể thêm nhiều phương thức kiểm tra nếu cần.


điều này không gây ra vấn đề truyền trong lớp con? nếu X có phương thức a và Y thừa hưởng X nhưng cũng có phương thức b. Khi bạn phân lớp lớp kiểm tra của bạn, bạn không phải chuyển biến trừu tượng của mình thành Y để thực hiện kiểm tra trên b?
Johnno Nolan

8

Để thực hiện một thử nghiệm đơn vị cụ thể trên lớp trừu tượng, bạn nên lấy nó cho mục đích thử nghiệm, kiểm tra kết quả base.method () và hành vi dự định khi kế thừa.

Bạn kiểm tra một phương thức bằng cách gọi nó để kiểm tra một lớp trừu tượng bằng cách thực hiện nó ...


8

Nếu lớp trừu tượng của bạn chứa chức năng cụ thể có giá trị kinh doanh, thì tôi thường sẽ kiểm tra trực tiếp bằng cách tạo một bài kiểm tra nhân đôi dữ liệu trừu tượng hoặc bằng cách sử dụng khung mô phỏng để thực hiện điều này cho tôi. Cái nào tôi chọn phụ thuộc rất nhiều vào việc tôi có cần viết các triển khai thử nghiệm cụ thể của các phương thức trừu tượng hay không.

Kịch bản phổ biến nhất mà tôi cần thực hiện điều này là khi tôi đang sử dụng mẫu Phương thức mẫu , chẳng hạn như khi tôi đang xây dựng một loại khung mở rộng nào đó sẽ được sử dụng bởi bên thứ 3. Trong trường hợp này, lớp trừu tượng là thứ định nghĩa thuật toán mà tôi muốn kiểm tra, vì vậy sẽ có ý nghĩa hơn khi kiểm tra cơ sở trừu tượng hơn là một triển khai cụ thể.

Tuy nhiên, tôi nghĩ điều quan trọng là các thử nghiệm này chỉ nên tập trung vào các triển khai cụ thể của logic kinh doanh thực tế ; bạn không nên đơn vị thực hiện kiểm tra chi tiết của lớp trừu tượng vì bạn sẽ kết thúc với các bài kiểm tra dễ vỡ.


6

một cách là viết một trường hợp thử nghiệm trừu tượng tương ứng với lớp trừu tượng của bạn, sau đó viết các trường hợp thử nghiệm cụ thể phân lớp trường hợp thử nghiệm trừu tượng của bạn. làm điều này cho mỗi lớp con cụ thể của lớp trừu tượng ban đầu của bạn (tức là hệ thống phân cấp trường hợp thử nghiệm của bạn phản ánh hệ thống phân cấp lớp của bạn). xem Kiểm tra giao diện trong sách người nhận Junit: http://safari.informit.com/9781932394238/ch02lev1sec6 .

cũng thấy testcase lớp cha trong mẫu xUnit: http://xunitpatterns.com/Testcase%20Superclass.html


4

Tôi sẽ tranh luận chống lại các bài kiểm tra "trừu tượng". Tôi nghĩ rằng một bài kiểm tra là một ý tưởng cụ thể và không có sự trừu tượng. Nếu bạn có các phần tử chung, hãy đặt chúng trong các phương thức hoặc lớp trợ giúp cho mọi người sử dụng.

Đối với việc kiểm tra một lớp kiểm tra trừu tượng, hãy chắc chắn rằng bạn tự hỏi mình đang kiểm tra cái gì. Có một số cách tiếp cận và bạn nên tìm hiểu những gì hoạt động trong kịch bản của bạn. Bạn đang cố gắng thử nghiệm một phương thức mới trong lớp con của bạn? Sau đó, có các bài kiểm tra của bạn chỉ tương tác với phương pháp đó. Bạn đang thử nghiệm các phương thức trong lớp cơ sở của bạn? Sau đó, có lẽ chỉ có một vật cố định riêng cho lớp đó và kiểm tra từng phương thức riêng lẻ với càng nhiều thử nghiệm cần thiết.


Tôi không muốn kiểm tra lại mã mà tôi đã kiểm tra đó là lý do tại sao tôi đi vào con đường thử nghiệm trừu tượng. Tôi đang cố gắng kiểm tra tất cả các phương thức cụ thể trong lớp trừu tượng của tôi ở một nơi.
Paul Whelan

7
Tôi không đồng ý với việc trích xuất các phần tử phổ biến cho các lớp trợ giúp, ít nhất là trong một số trường hợp (nhiều?). Nếu một lớp trừu tượng có chứa một số chức năng cụ thể, tôi nghĩ rằng nó hoàn toàn có thể chấp nhận để kiểm tra đơn vị chức năng đó trực tiếp.
Seth Petry-Johnson

4

Đây là mẫu tôi thường làm theo khi thiết lập khai thác để kiểm tra một lớp trừu tượng:

public abstract class MyBase{
  /*...*/
  public abstract void VoidMethod(object param1);
  public abstract object MethodWithReturn(object param1);
  /*,,,*/
}

Và phiên bản tôi sử dụng đang thử nghiệm:

public class MyBaseHarness : MyBase{
  /*...*/
  public Action<object> VoidMethodFunction;
  public override void VoidMethod(object param1){
    VoidMethodFunction(param1);
  }
  public Func<object, object> MethodWithReturnFunction;
  public override object MethodWithReturn(object param1){
    return MethodWihtReturnFunction(param1);
  }
  /*,,,*/
}

Nếu các phương thức trừu tượng được gọi khi tôi không mong đợi, các thử nghiệm thất bại. Khi sắp xếp các bài kiểm tra, tôi có thể dễ dàng bỏ qua các phương thức trừu tượng với lambdas thực hiện các xác nhận, ném ngoại lệ, trả về các giá trị khác nhau, v.v.


3

Nếu các phương thức cụ thể gọi bất kỳ phương thức trừu tượng nào mà chiến lược sẽ không hoạt động và bạn muốn kiểm tra riêng từng hành vi của lớp con. Mặt khác, mở rộng nó và khai thác các phương thức trừu tượng như bạn đã mô tả sẽ ổn, một lần nữa cung cấp các phương thức cụ thể của lớp trừu tượng được tách rời khỏi các lớp con.


2

Tôi cho rằng bạn có thể muốn kiểm tra chức năng cơ bản của một lớp trừu tượng ... Nhưng có lẽ bạn sẽ tốt nhất bằng cách mở rộng lớp mà không ghi đè bất kỳ phương thức nào và thực hiện chế độ nhạo báng tối thiểu cho các phương thức trừu tượng.


2

Một trong những động lực chính để sử dụng một lớp trừu tượng là cho phép đa hình trong ứng dụng của bạn - tức là: bạn có thể thay thế một phiên bản khác trong thời gian chạy. Trong thực tế, điều này rất giống với việc sử dụng một giao diện ngoại trừ lớp trừu tượng cung cấp một số hệ thống ống nước phổ biến, thường được gọi là một mẫu Mẫu .

Từ góc độ thử nghiệm đơn vị, có hai điều cần xem xét:

  1. Tương tác của lớp trừu tượng của bạn với nó các lớp liên quan . Sử dụng khung thử nghiệm giả là lý tưởng cho kịch bản này vì nó cho thấy lớp trừu tượng của bạn chơi tốt với các lớp khác.

  2. Chức năng của các lớp dẫn xuất . Nếu bạn có logic tùy chỉnh mà bạn đã viết cho các lớp dẫn xuất của mình, bạn nên kiểm tra các lớp đó một cách riêng rẽ.

chỉnh sửa: RhinoMocks là một khung thử nghiệm giả tuyệt vời có thể tạo các đối tượng giả trong thời gian chạy bằng cách tự động xuất phát từ lớp của bạn. Cách tiếp cận này có thể giúp bạn tiết kiệm vô số giờ của các lớp dẫn xuất mã hóa bằng tay.


2

Đầu tiên nếu lớp trừu tượng chứa một số phương thức cụ thể tôi nghĩ bạn nên làm điều này xem xét ví dụ này

 public abstract class A 

 {

    public boolean method 1
    {
        // concrete method which we have to test.

    }


 }


 class B extends class A

 {

      @override
      public boolean method 1
      {
        // override same method as above.

      }


 } 


  class Test_A 

  {

    private static B b;  // reference object of the class B

    @Before
    public void init()

      {

      b = new B ();    

      }

     @Test
     public void Test_method 1

       {

       b.method 1; // use some assertion statements.

       }

   }

1

Nếu một lớp trừu tượng phù hợp với việc triển khai của bạn, hãy kiểm tra (như được đề xuất ở trên) một lớp cụ thể dẫn xuất. Giả định của bạn là chính xác.

Để tránh nhầm lẫn trong tương lai, hãy lưu ý rằng lớp kiểm tra cụ thể này không phải là giả, mà là giả .

Theo nghĩa chặt chẽ, một giả được xác định bởi các đặc điểm sau:

  • Một giả được sử dụng thay cho mỗi và mọi phụ thuộc của lớp chủ đề đang được kiểm tra.
  • Giả là một triển khai giả của giao diện (bạn có thể nhớ rằng theo quy tắc chung, các phụ thuộc phải được khai báo là giao diện; khả năng kiểm tra là một lý do chính cho việc này)
  • Các hành vi của các thành viên giao diện của giả - cho dù các phương thức hoặc thuộc tính - được cung cấp tại thời điểm thử nghiệm (một lần nữa, bằng cách sử dụng khung mô phỏng). Bằng cách này, bạn tránh kết hợp việc thực hiện đang được thử nghiệm với việc thực hiện các phụ thuộc của nó (tất cả nên có các thử nghiệm riêng biệt).

1

Theo câu trả lời của @ patrick-desjardins, tôi đã triển khai lớp trừu tượng và đó là lớp triển khai cùng với @Testnhư sau:

Lớp trừu tượng - ABC.java

import java.util.ArrayList;
import java.util.List;

public abstract class ABC {

    abstract String sayHello();

    public List<String> getList() {
        final List<String> defaultList = new ArrayList<>();
        defaultList.add("abstract class");
        return defaultList;
    }
}

các lớp Trừu tượng không thể được khởi tạo, nhưng chúng có thể được phân lớp , lớp cụ thể DEF.java , như sau:

public class DEF extends ABC {

    @Override
    public String sayHello() {
        return "Hello!";
    }
}

Lớp @Test để kiểm tra cả phương pháp trừu tượng cũng như không trừu tượng:

import org.junit.Before;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.contains;
import java.util.Collection;
import java.util.List;
import static org.hamcrest.Matchers.equalTo;

import org.junit.Test;

public class DEFTest {

    private DEF def;

    @Before
    public void setup() {
        def = new DEF();
    }

    @Test
    public void add(){
        String result = def.sayHello();
        assertThat(result, is(equalTo("Hello!")));
    }

    @Test
    public void getList(){
        List<String> result = def.getList();
        assertThat((Collection<String>) result, is(not(empty())));
        assertThat(result, contains("abstract class"));
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.