Làm thế nào để viết bài kiểm tra đơn vị trước khi tái cấu trúc?


55

Tôi đã đọc một số câu trả lời cho các câu hỏi dọc theo một dòng tương tự, chẳng hạn như "Làm thế nào để bạn giữ cho các bài kiểm tra đơn vị của bạn hoạt động khi tái cấu trúc?". Trong trường hợp của tôi, kịch bản hơi khác ở chỗ tôi đã được đưa ra một dự án để xem xét và đưa ra một số tiêu chuẩn mà chúng tôi có, hiện tại không có thử nghiệm nào cho dự án!

Tôi đã xác định được một số điều tôi nghĩ có thể đã được thực hiện tốt hơn như KHÔNG trộn mã loại DAO trong một lớp dịch vụ.

Trước khi tái cấu trúc, có vẻ như là một ý tưởng tốt để viết các bài kiểm tra cho mã hiện có. Vấn đề xảy ra với tôi là khi tôi thực hiện tái cấu trúc thì các bài kiểm tra đó sẽ bị hỏng khi tôi thay đổi khi thực hiện logic nhất định và các bài kiểm tra sẽ được viết với cấu trúc trước đó (phụ thuộc giả định, v.v.)

Trong trường hợp của tôi, cách tốt nhất để xử lý là gì? Tôi muốn viết các bài kiểm tra xung quanh mã được cấu trúc lại nhưng tôi biết có nguy cơ tôi có thể cấu trúc lại những thứ không chính xác có thể thay đổi hành vi mong muốn.

Cho dù đây là cấu trúc lại hay thiết kế lại, tôi rất vui vì hiểu được các thuật ngữ đó để sửa, hiện tại tôi đang làm việc theo định nghĩa sau để tái cấu trúc "Với định nghĩa lại, theo định nghĩa, bạn không thay đổi phần mềm của mình làm gì, bạn thay đổi cách thực hiện nó. ". Vì vậy, tôi không thay đổi những gì phần mềm tôi sẽ thay đổi như thế nào / nơi nó làm.

Tương tự, tôi có thể thấy lập luận rằng nếu tôi thay đổi chữ ký của các phương thức có thể được coi là thiết kế lại.

Đây là một ví dụ ngắn gọn

MyDocumentService.java (hiện hành)

public class MyDocumentService {
   ...
   public List<Document> findAllDocuments() {
      DataResultSet rs = documentDAO.findAllDocuments();
      List<Document> documents = new ArrayList<>();
      for(DataObject do: rs.getRows()) {
         //get row data create new document add it to 
         //documents list
      }

      return documents;
   }
}

MyDocumentService.java (tái cấu trúc / thiết kế lại bất cứ điều gì)

public class MyDocumentService {
   ...
   public List<Document> findAllDocuments() {
      //Code dealing with DataResultSet moved back up to DAO
      //DAO now returns a List<Document> instead of a DataResultSet
      return documentDAO.findAllDocuments();
   }
}

14
Có thực sự tái cấu trúc bạn dự định làm, hoặc thiết kế lại ? Bởi vì câu trả lời có thể khác nhau trong hai trường hợp.
Herby

4
Tôi đang làm việc theo định nghĩa "Với tái cấu trúc, theo định nghĩa, bạn không thay đổi những gì phần mềm của bạn làm, bạn thay đổi cách thức hoạt động của nó." Vì vậy, tôi tin rằng trong trường hợp này là tái cấu trúc, vui lòng sửa lại cách hiểu của tôi về thuật ngữ này
PDStat

21
Đừng, viết các bài kiểm tra tích hợp. "Tái cấu trúc" mà bạn đang lập kế hoạch cao hơn mức thử nghiệm đơn vị. Chỉ đơn vị kiểm tra các lớp mới (hoặc lớp cũ mà bạn biết bạn đang giữ chúng).
Ngừng làm hại Monica

2
Liên quan đến định nghĩa tái cấu trúc, phần mềm của bạn có xác định rõ ràng những gì nó làm không? Nói cách khác, nó đã được "bao thanh toán" thành các mô-đun với các API độc lập chưa? Nếu không, thì bạn không thể cấu trúc lại nó, ngoại trừ có lẽ ở mức cao nhất (đối mặt với người dùng). Ở cấp độ mô-đun, chắc chắn bạn sẽ thiết kế lại nó. Trong trường hợp đó, đừng lãng phí thời gian của bạn để viết bài kiểm tra đơn vị trước khi bạn có đơn vị.
Kevin Krumwiede

4
Bạn rất có thể sẽ phải thực hiện một chút tái cấu trúc mà không có mạng lưới kiểm tra an toàn chỉ để có thể đưa nó vào khai thác thử nghiệm. Lời khuyên tốt nhất tôi có thể cung cấp cho bạn là nếu IDE hoặc công cụ tái cấu trúc của bạn sẽ không làm điều đó cho bạn, đừng làm điều đó bằng tay. Tiếp tục áp dụng tái cấu trúc tự động cho đến khi bạn có thể đưa CUT vào khai thác. Bạn chắc chắn sẽ muốn chọn một bản sao "Hoạt động hiệu quả với Bộ luật kế thừa" của Michael Feather.
RubberDuck

Câu trả lời:


56

Bạn đang tìm kiếm các bài kiểm tra kiểm tra hồi quy . tức là phá vỡ một số hành vi hiện có. Tôi sẽ bắt đầu bằng cách xác định ở mức độ nào hành vi đó sẽ giữ nguyên và giao diện điều khiển hành vi đó sẽ giữ nguyên và bắt đầu thử nghiệm tại thời điểm đó.

Bây giờ bạn có một số bài kiểm tra sẽ khẳng định rằng bất cứ điều gì bạn làm dưới mức này, hành vi của bạn vẫn giữ nguyên.

Bạn hoàn toàn đúng khi đặt câu hỏi làm thế nào các bài kiểm tra và mã có thể duy trì đồng bộ. Nếu giao diện của bạn với một thành phần vẫn giữ nguyên, thì bạn có thể viết một bài kiểm tra xung quanh điều này và khẳng định các điều kiện giống nhau cho cả hai triển khai (khi bạn tạo triển khai mới). Nếu không, thì bạn phải chấp nhận rằng thử nghiệm cho một thành phần dự phòng là thử nghiệm dự phòng.


1
Viz, bạn có thể đang thực hiện kiểm thử tích hợp hoặc hệ thống thay vì kiểm tra đơn vị. Bạn có thể vẫn sẽ sử dụng công cụ "thử nghiệm đơn vị" cho nó, nhưng bạn sẽ đạt được nhiều hơn một đơn vị mã với mỗi thử nghiệm.
Móż 17/05/2016

Đúng. Đó là rất nhiều trường hợp. Kiểm tra hồi quy của bạn cũng có thể thực hiện một cái gì đó ở mức rất cao, ví dụ như yêu cầu REST đến máy chủ và có thể là kiểm tra cơ sở dữ liệu tiếp theo (nghĩa là không phải kiểm tra đơn vị !)
Brian Agnew

40

Thực tiễn được đề xuất là bắt đầu bằng cách viết "kiểm tra pin-down" để kiểm tra hành vi hiện tại của mã, có thể bao gồm các lỗi, nhưng không yêu cầu bạn rơi vào sự điên rồ của việc sáng suốt liệu một hành vi nhất định vi phạm các tài liệu yêu cầu có phải là lỗi không, giải pháp cho một cái gì đó bạn không biết hoặc thể hiện một sự thay đổi không có giấy tờ trong các yêu cầu.

Điều hợp lý nhất là các thử nghiệm pin-down này ở mức cao, nghĩa là tích hợp thay vì thử nghiệm đơn vị, để chúng sẽ tiếp tục hoạt động khi bạn bắt đầu tái cấu trúc.

Nhưng một số phép tái cấu trúc có thể cần thiết để làm cho mã có thể kiểm tra được - chỉ cần cẩn thận để tuân theo các phép tái cấu trúc "an toàn". Ví dụ, trong hầu hết các trường hợp, các phương thức là riêng tư có thể được công khai mà không vi phạm bất cứ điều gì.


+1 cho các bài kiểm tra tích hợp. Tùy thuộc vào ứng dụng, bạn có thể bắt đầu ở cấp độ thực sự gửi yêu cầu đến ứng dụng web. Những gì ứng dụng gửi lại không nên thay đổi chỉ vì tái cấu trúc, mặc dù nếu nó gửi lại HTML, điều này chắc chắn ít kiểm tra hơn.
jpmc26

Tôi thích cụm từ 'pin-down' thử nghiệm.
Brian Agnew

12

Tôi đề nghị - nếu bạn chưa - đọc cả Làm việc hiệu quả với Mã kế thừa cũng như Tái cấu trúc - Cải thiện Thiết kế của Mã hiện tại .

. ..]

Tôi không nhất thiết phải coi đây là một vấn đề: Viết các bài kiểm tra, thay đổi cấu trúc của mã của bạn, và sau đó điều chỉnh cấu trúc bài thi cũng . Điều này sẽ cung cấp cho bạn thông tin phản hồi trực tiếp xem cấu trúc mới của bạn có thực sự tốt hơn cấu trúc cũ hay không, bởi vì nếu có, các bài kiểm tra được điều chỉnh sẽ dễ viết hơn (và do đó thay đổi các bài kiểm tra nên tương đối đơn giản, giảm nguy cơ có một bài mới được giới thiệu lỗi vượt qua các bài kiểm tra).

Ngoài ra, như những người khác đã viết: Đừng viết các bài kiểm tra quá chi tiết (ít nhất là không phải lúc đầu). Cố gắng duy trì ở mức độ trừu tượng cao (do đó các bài kiểm tra của bạn có thể sẽ được đặc trưng tốt hơn là hồi quy hoặc thậm chí kiểm tra tích hợp).


1
Điều này. Các xét nghiệm sẽ có vẻ khủng khiếp , nhưng chúng sẽ bao gồm các hành vi hiện có. Sau đó, khi mã được tái cấu trúc, hãy thực hiện các bài kiểm tra, trong bước khóa. Lặp lại cho đến khi bạn có một cái gì đó bạn tự hào. ++
RubberDuck

1
Tôi thứ hai cả hai khuyến nghị cuốn sách - Tôi luôn có sẵn cả hai khi tôi phải đối phó với mã không kiểm tra.
Toby Speight

5

Đừng viết các bài kiểm tra đơn vị nghiêm ngặt trong đó bạn chế nhạo tất cả các phụ thuộc. Một số người sẽ cho bạn biết đây không phải là bài kiểm tra đơn vị thực sự. Mặc kệ họ. Những xét nghiệm này rất hữu ích và đó là những gì quan trọng.

Hãy xem ví dụ của bạn:

public class MyDocumentService {
   ...
   public List<Document> findAllDocuments() {
      DataResultSet rs = documentDAO.findAllDocuments();
      List<Document> documents = new ArrayList<>();
      for(DataObject do: rs.getRows()) {
         //get row data create new document add it to 
         //documents list
      }

      return documents;
   }
}

Bài kiểm tra của bạn có thể trông giống như thế này:

DocumentDao documentDao = Mock.create(DocumentDao.class);
Mock.when(documentDao.findAllDocuments())
    .thenReturn(DataResultSet.create(...))
assertEquals(..., new MyDocumentService(documentDao).findAllDocuments());

Thay vì chế nhạo DocumentDao, hãy chế giễu các phụ thuộc của nó:

DocumentDao documentDao = new DocumentDao(db);
Mock.when(db...)
    .thenReturn(...)
assertEquals(..., new MyDocumentService(documentDao).findAllDocuments());

Bây giờ, bạn có thể di chuyển logic từ MyDocumentServicevào DocumentDaomà không cần kiểm tra phá vỡ. Các thử nghiệm sẽ cho thấy chức năng là như nhau (cho đến nay bạn đã thử nghiệm nó).


Nếu bạn đang kiểm tra DocumentService và không chế nhạo DAO, thì đó hoàn toàn không phải là thử nghiệm đơn vị. Đó là một cái gì đó ở giữa thử nghiệm đơn nhất và tích hợp. Không phải nó?
Laiv

7
@Laiv, thực sự có sự đa dạng đáng kể trong cách mọi người sử dụng bài kiểm tra đơn vị thuật ngữ. Một số sử dụng nó có nghĩa là chỉ kiểm tra cô lập nghiêm ngặt. Những người khác bao gồm bất kỳ bài kiểm tra nào chạy nhanh. Một số bao gồm bất cứ điều gì chạy trong một khung kiểm tra. Nhưng cuối cùng, việc bạn muốn xác định bài kiểm tra đơn vị như thế nào không quan trọng. Câu hỏi là những bài kiểm tra nào hữu ích, vì vậy chúng ta không nên bị phân tâm bởi cách chúng ta xác định chính xác bài kiểm tra đơn vị.
Winston Ewert

Điểm tuyệt vời cho thấy sự hữu ích là điều quan trọng nhất. Các thử nghiệm đơn vị ngông cuồng cho các thuật toán tầm thường nhất chỉ vì việc thử nghiệm đơn vị gây hại nhiều hơn lợi, nếu không chỉ là lãng phí thời gian và tài nguyên quý giá. Điều này có thể được áp dụng cho tất cả mọi thứ và là điều tôi ước mình biết trước đó trong sự nghiệp.
Lee

3

Như bạn nói, nếu bạn thay đổi hành vi thì đó là một phép biến đổi và không phải là phép tái cấu trúc. Ở cấp độ nào bạn thay đổi hành vi là điều làm nên sự khác biệt.

Nếu không có thử nghiệm chính thức nào ở mức cao nhất thì hãy thử và tìm một tập hợp các yêu cầu mà khách hàng (gọi mã hoặc con người) cần giữ nguyên sau khi thiết kế lại để mã của bạn được coi là hoạt động. Đó là danh sách các trường hợp thử nghiệm bạn cần thực hiện.

Để giải quyết câu hỏi của bạn về việc thay đổi triển khai yêu cầu thay đổi trường hợp thử nghiệm, tôi khuyên bạn nên xem qua TDD (cổ điển) vs London (mockist). Martin Fowler nói về điều này trong bài viết tuyệt vời của mình Mocks không sơ khai nhưng nhiều người có ý kiến. Nếu bạn bắt đầu ở cấp độ cao nhất, nơi mà các phần bên ngoài của bạn không thể thay đổi và giảm dần thì các yêu cầu sẽ duy trì khá ổn định cho đến khi bạn đạt đến một mức độ thực sự cần thay đổi.

Nếu không có bất kỳ thử nghiệm nào thì điều này sẽ khó khăn và bạn có thể muốn xem xét việc chạy các máy khách thông qua các đường dẫn mã kép (và ghi lại sự khác biệt) cho đến khi bạn có thể chắc chắn rằng mã mới của bạn thực hiện chính xác những gì nó cần làm.


3

Đây là cách tiếp cận của tôi. Nó có chi phí về thời gian vì đây là một bài kiểm tra tái cấu trúc theo 4 giai đoạn.

Những gì tôi sẽ phơi bày có thể phù hợp với các thành phần có độ phức tạp cao hơn so với những gì được nêu trong ví dụ của câu hỏi.

Dù sao, chiến lược là hợp lệ cho bất kỳ ứng cử viên thành phần nào được chuẩn hóa bởi một giao diện (DAO, Dịch vụ, Bộ điều khiển, ...).

1. Giao diện

Cho phép tập hợp tất cả các phương thức công khai từ MyDocumentService và cho phép đặt tất cả chúng vào cùng một giao diện. Ví dụ. Nếu nó đã tồn tại, hãy sử dụng cái đó thay vì đặt bất kỳ cái mới nào .

public interface DocumentService {

   List<Document> getAllDocuments();

   //more methods here...
}

Sau đó, chúng tôi buộc MyDocumentService triển khai giao diện mới này.

Càng xa càng tốt. Không có thay đổi lớn nào được thực hiện, chúng tôi tôn trọng hợp đồng hiện tại và các hành vi vẫn chưa được xử lý.

public class MyDocumentService implements DocumentService {

 @Override
 public List<Document> getAllDocuments(){
         //legacy code here as it is.
        // with no changes ...
  }
}

2. Kiểm tra đơn vị mã kế thừa

Ở đây chúng tôi có công việc khó khăn. Để thiết lập một bộ thử nghiệm. Chúng ta nên đặt càng nhiều trường hợp càng tốt: trường hợp thành công và cả trường hợp lỗi. Những cái cuối cùng là vì chất lượng của kết quả.

Bây giờ, thay vì kiểm tra MyDocumentService, chúng tôi sẽ sử dụng giao diện làm hợp đồng sẽ được kiểm tra.

Tôi sẽ không đi vào chi tiết, vì vậy hãy tha thứ cho tôi Nếu mã của tôi trông quá đơn giản hoặc quá bất khả tri

public class DocumentServiceTestSuite {

   @Mock
   MyDependencyA mockDepA;

   @Mock
   MyDependencyB mockDepB;

    //... More mocks

   DocumentService service;

  @Before
   public void initService(){
       service = MyDocumentService(mockDepA, mockDepB);
      //this is purposed way to inject 
      //dependencies. Replace it with one you like more.  
   }

   @Test
   public void getAllDocumentsOK(){
         // here I mock depA and depB
         // wanted behaivors...

         List<Document> result = service.getAllDocuments();

          Assert.assertX(result);
          Assert.assertY(result);
           //... As many you think appropiate
    } 
 }

Giai đoạn này mất nhiều thời gian hơn bất kỳ cách nào khác trong phương pháp này. Và đó là điều quan trọng nhất vì nó sẽ đặt điểm tham chiếu cho các so sánh trong tương lai.

Lưu ý: Do không có thay đổi lớn nào được thực hiện và hành vi vẫn chưa được xử lý. Tôi đề nghị làm một thẻ ở đây vào SCM. Thẻ hoặc chi nhánh không quan trọng. Chỉ cần làm một phiên bản.

Chúng tôi muốn nó cho các bản rollback, so sánh các phiên bản và có thể là để thực thi song song mã cũ và mã mới.

3. Tái cấu trúc

Refactor sẽ được thực hiện thành một thành phần mới. Chúng tôi sẽ không làm bất kỳ thay đổi trên mã hiện có. Bước đầu tiên là dễ dàng như sao chép và dán MyDocumentService và đổi tên thành CustomDocumentService (ví dụ).

Lớp mới tiếp tục triển khai DocumentService . Sau đó đi và tái cấu trúc getAllDocument () . (Hãy bắt đầu bằng một. Bộ tái cấu trúc pin)

Nó có thể yêu cầu một số thay đổi trên giao diện / phương thức của DAO. Nếu vậy, đừng thay đổi mã hiện có. Thực hiện phương pháp của riêng bạn trong giao diện DAO. Chú thích mã cũ là Không dùng nữa và bạn sẽ biết sau này về những gì cần xóa.

Điều quan trọng là không phá vỡ / thay đổi triển khai hiện có. Chúng tôi muốn thực hiện song song cả hai dịch vụ và sau đó so sánh kết quả.

public class CustomDocumentService implements DocumentService {

 @Override
 public List<Document> getAllDocuments(){
         //new code here ...
         //due to im refactoring service 
         //I do the less changes possible on its dependencies (DAO).
         //these changes will come later 
         //and they will have their own tests
  }
 }

4. Cập nhật DocumentServiceTestSuite

Ok, bây giờ là phần dễ dàng hơn. Để thêm các thử nghiệm của thành phần mới.

public class DocumentServiceTestSuite {

   @Mock
   MyDependencyA mockDepA;

   @Mock
   MyDependencyB mockDepB;

   DocumentService service;
   DocumentService customService;

  @Before
   public void initService(){
       service = MyDocumentService(mockDepA, mockDepB);
        customService = CustomDocumentService(mockDepA, mockDepB);
       // this is purposed way to inject 
       //dependencies. Replace it with the one you like more
   }

   @Test
   public void getAllDocumentsOK(){
         // here I mock depA and depB
         // wanted behaivors...

         List<Document> oldResult = service.getAllDocuments();

          Assert.assertX(oldResult);
          Assert.assertY(oldResult);
           //... As many you think appropiate

          List<Document> newResult = customService.getAllDocuments();

          Assert.assertX(newResult);
          Assert.assertY(newResult);
           //... The very same made to oldResult

          //this is optional
Assert.assertEquals(oldResult,newResult);
    } 
 }

Bây giờ chúng ta có oldResult và newResult đều được xác nhận độc lập nhưng chúng ta cũng có thể so sánh với nhau. Xác nhận cuối cùng này là tùy chọn và nó phụ thuộc vào kết quả. Có thể không thể so sánh được.

Có thể không tạo ra quá nhiều sự phức tạp để so sánh hai bộ sưu tập theo cách này, nhưng sẽ hợp lệ cho bất kỳ loại đối tượng nào khác (pojos, thực thể mô hình dữ liệu, DTOs, Wrappers, kiểu bản địa ...)

Ghi chú

Tôi sẽ không dám nói làm thế nào để kiểm tra đơn vị hoặc làm thế nào để sử dụng libs giả. Tôi không dám nói bạn phải làm thế nào để tái cấu trúc. Những gì tôi muốn làm là đề xuất một chiến lược toàn cầu. Làm thế nào để đưa nó về phía trước phụ thuộc vào bạn. Bạn biết chính xác mã là như thế nào, độ phức tạp của nó và nếu chiến lược như vậy đáng để thử. Sự kiện như thời gian và tài nguyên quan trọng ở đây. Cũng quan trọng những gì bạn mong đợi từ các thử nghiệm này trong tương lai.

Tôi đã bắt đầu các ví dụ của mình bằng một Dịch vụ và tôi sẽ làm theo DAO, v.v. Đi sâu vào mức độ phụ thuộc. Ít nhiều nó có thể được mô tả như là chiến lược từ dưới lên . Tuy nhiên, đối với các thay đổi / bộ tái cấu trúc nhỏ ( như ví dụ được hiển thị trong ví dụ về chuyến tham quan ), từ dưới lên sẽ thực hiện công việc dễ dàng hơn. Bởi vì phạm vi của những thay đổi là ít.

Cuối cùng, tùy thuộc vào bạn để loại bỏ mã không dùng nữa và để chuyển hướng phụ thuộc cũ sang mã mới.

Loại bỏ các bài kiểm tra cũng không dùng nữa và công việc được thực hiện. Nếu bạn đã phiên bản giải pháp cũ với các thử nghiệm của nó, bạn có thể kiểm tra và so sánh lẫn nhau bất cứ lúc nào.

Do hậu quả của rất nhiều công việc, bạn có mã kế thừa được kiểm tra, xác nhận và phiên bản. Và mã mới, đã được thử nghiệm, xác nhận và sẵn sàng để được phiên bản.


3

tl; dr Đừng viết bài kiểm tra đơn vị. Viết bài kiểm tra ở mức độ phù hợp hơn.


Đưa ra định nghĩa làm việc của bạn về tái cấu trúc:

bạn không thay đổi những gì phần mềm của bạn làm, bạn thay đổi cách nó làm

có phổ rất rộng. Một đầu là một thay đổi độc lập đối với phương pháp cụ thể, có lẽ sử dụng thuật toán hiệu quả hơn. Ở đầu bên kia là chuyển sang ngôn ngữ khác.

Bất kể mức tái cấu trúc / thiết kế lại nào đang được thực hiện, điều quan trọng là phải có các thử nghiệm hoạt động ở cấp độ đó hoặc cao hơn.

Kiểm tra tự động thường được phân loại theo cấp độ như:

  • Kiểm tra đơn vị - Các thành phần riêng lẻ (lớp, phương thức)

  • Kiểm tra tích hợp - Tương tác giữa các thành phần

  • Kiểm tra hệ thống - Ứng dụng hoàn chỉnh

Viết mức độ kiểm tra có thể chịu đựng được việc tái cấu trúc về cơ bản chưa được xử lý.

Hãy suy nghĩ:

Những hành vi thiết yếu, có thể nhìn thấy công khai nào mà ứng dụng sẽ có cả trướcsau khi tái cấu trúc? Làm thế nào tôi có thể kiểm tra điều đó vẫn hoạt động như nhau?


2

Đừng lãng phí thời gian viết các bài kiểm tra liên quan đến các điểm mà bạn có thể dự đoán rằng giao diện sẽ thay đổi theo cách không tầm thường. Đây thường là một dấu hiệu cho thấy bạn đang cố gắng kiểm tra các lớp đơn vị 'hợp tác' về bản chất - có giá trị không nằm ở chính chúng, mà là cách chúng tương tác với một số lớp liên quan chặt chẽ để tạo ra hành vi có giá trị . Đó là rằng hành vi mà bạn muốn kiểm tra, có nghĩa là bạn muốn được thử nghiệm ở một mức độ cao hơn. Kiểm tra dưới mức này thường đòi hỏi rất nhiều sự chế nhạo xấu xí, và các thử nghiệm kết quả có thể là một lực cản đối với sự phát triển hơn là một sự trợ giúp cho hành vi bảo vệ.

Đừng quá bận tâm về việc bạn đang thực hiện tái cấu trúc, thiết kế lại hay bất cứ điều gì. Bạn có thể thực hiện các thay đổi ở cấp thấp hơn tạo thành thiết kế lại một số thành phần, nhưng ở cấp độ tích hợp cao hơn chỉ đơn giản là số tiền cho một cấu trúc lại. Vấn đề là phải rõ ràng về hành vi nào có giá trị đối với bạn và bảo vệ hành vi đó khi bạn đi.

Có thể hữu ích khi xem xét khi bạn viết bài kiểm tra của mình - tôi có thể dễ dàng mô tả cho QA, chủ sở hữu sản phẩm hoặc người dùng, thử nghiệm này thực sự đang thử nghiệm gì không? Nếu có vẻ như mô tả thử nghiệm sẽ quá bí truyền và kỹ thuật, có thể bạn đang thử nghiệm ở cấp độ sai. Kiểm tra tại các điểm / cấp độ 'có ý nghĩa' và không kiểm tra mã của bạn bằng các bài kiểm tra ở mọi cấp độ.


Luôn quan tâm đến lý do cho downvote!
topo morto

1

Nhiệm vụ đầu tiên của bạn là cố gắng đưa ra "chữ ký phương pháp lý tưởng" cho các bài kiểm tra của mình. Phấn đấu để làm cho nó một chức năng thuần túy . Điều này phải độc lập với mã thực sự đang được thử nghiệm; nó là một lớp tiếp hợp nhỏ Viết mã của bạn vào lớp bộ điều hợp này. Bây giờ khi bạn cấu trúc lại mã của mình, bạn chỉ cần thay đổi lớp bộ điều hợp. Đây là một ví dụ đơn giản:

[TestMethod]
public void simple_addition()
{
    Assert.AreEqual(7, Eval("3 + 4"));
}

[TestMethod]
public void order_of_operations()
{
    Assert.AreEqual(52, Eval("2 + 5 * 10"));
}

[TestMethod]
public void absolute_value()
{
    Assert.AreEqual(9, Eval("abs(-9)"));
    Assert.AreEqual(5, Eval("abs(5)"));
    Assert.AreEqual(0, Eval("abs(0)"));
}

static object Eval(string expression)
{
    // This is the code under test.
    // I can refactor this as much as I want without changing the tests.
    var settings = new EvaluatorSettings();
    Evaluator.Settings = settings;
    Evaluator.Evaluate(expression);
    return Evaluator.LastResult;
}

Các thử nghiệm là tốt, nhưng mã được thử nghiệm có API xấu. Tôi có thể cấu trúc lại nó mà không thay đổi các bài kiểm tra đơn giản bằng cách cập nhật lớp bộ điều hợp của mình:

static object Eval(string expression)
{
    // After refactoring...
    var settings = new EvaluatorSettings();
    var evaluator = new Evaluator(settings);
    return evaluator.Evaluate(expression);
}

Ví dụ này có vẻ là một điều khá rõ ràng phải làm theo nguyên tắc Đừng lặp lại chính mình, nhưng nó có thể không quá rõ ràng trong các trường hợp khác. Lợi thế vượt xa DRY - lợi thế thực sự là tách rời các thử nghiệm khỏi mã được thử nghiệm.

Tất nhiên, kỹ thuật này có thể không được khuyến khích trong mọi tình huống. Ví dụ: sẽ không có lý do để viết bộ điều hợp cho POCO / POJO vì họ không thực sự có API có thể thay đổi độc lập với mã kiểm tra. Ngoài ra nếu bạn đang viết một số lượng nhỏ các bài kiểm tra, một lớp bộ điều hợp tương đối lớn có thể sẽ bị lãng phí công sức.

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.